Skip to content

Instantly share code, notes, and snippets.

@matthewadams
Created November 27, 2025 14:01
Show Gist options
  • Select an option

  • Save matthewadams/ebfaf1c37ab0e5a0fca9bc73f7a1d863 to your computer and use it in GitHub Desktop.

Select an option

Save matthewadams/ebfaf1c37ab0e5a0fca9bc73f7a1d863 to your computer and use it in GitHub Desktop.
Automated docker engine install script for Debian that also supports Linux Mint
#!/usr/bin/env bash
#
# Docker Engine Installation Script for Debian, Ubuntu, and Linux Mint
# Run with: sudo ./install-docker-engine.sh [username...]
#
# Supports:
# - Debian (stable releases)
# - Ubuntu (and derivatives like Kubuntu, Lubuntu, Xubuntu)
# - Linux Mint (Ubuntu-based editions)
# - LMDE (Linux Mint Debian Edition)
#
# If no usernames provided, adds the SUDO_USER (the user who invoked sudo) to docker group.
# Multiple usernames can be provided to add them all to the docker group.
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
log_debug() { echo -e "${BLUE}[DEBUG]${NC} $*"; }
# Ensure script is run as root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run with sudo or as root."
exit 1
fi
# Determine which users to add to docker group
DOCKER_USERS=()
if [[ $# -gt 0 ]]; then
DOCKER_USERS=("$@")
else
# Try multiple methods to detect the user who invoked sudo
DETECTED_USER=""
# Method 1: SUDO_USER (set by sudo)
if [[ -n "${SUDO_USER:-}" ]] && [[ "$SUDO_USER" != "root" ]]; then
DETECTED_USER="$SUDO_USER"
# Method 2: LOGNAME (sometimes preserved)
elif [[ -n "${LOGNAME:-}" ]] && [[ "$LOGNAME" != "root" ]]; then
DETECTED_USER="$LOGNAME"
# Method 3: logname command (queries utmp)
elif command -v logname &>/dev/null; then
DETECTED_USER=$(logname 2>/dev/null || true)
[[ "$DETECTED_USER" == "root" ]] && DETECTED_USER=""
# Method 4: who am i (shows original login)
elif command -v who &>/dev/null; then
DETECTED_USER=$(who am i 2>/dev/null | awk '{print $1}' || true)
[[ "$DETECTED_USER" == "root" ]] && DETECTED_USER=""
fi
if [[ -n "$DETECTED_USER" ]]; then
DOCKER_USERS=("$DETECTED_USER")
log_info "Detected invoking user: $DETECTED_USER"
else
log_warn "Could not detect invoking user. No users will be added to docker group."
log_warn "Run manually after install: sudo usermod -aG docker YOUR_USERNAME"
fi
fi
# =============================================================================
# Detect distribution and determine Docker repository settings
# =============================================================================
# Helper function to extract value from /etc/os-release (more reliable than sourcing)
get_os_release_value() {
local key="$1"
local value=""
if [[ -f /etc/os-release ]]; then
value=$(grep "^${key}=" /etc/os-release 2>/dev/null | cut -d'=' -f2- | tr -d '"' || true)
fi
echo "$value"
}
DISTRO_ID=$(get_os_release_value "ID")
DISTRO_ID_LIKE=$(get_os_release_value "ID_LIKE")
DISTRO_NAME=$(get_os_release_value "NAME")
DISTRO_VERSION=$(get_os_release_value "VERSION_ID")
VERSION_CODENAME=$(get_os_release_value "VERSION_CODENAME")
UBUNTU_CODENAME=$(get_os_release_value "UBUNTU_CODENAME")
DEBIAN_CODENAME=$(get_os_release_value "DEBIAN_CODENAME")
# Fallback mapping for Linux Mint versions to Ubuntu codenames
# Used when UBUNTU_CODENAME is not present in /etc/os-release
get_mint_ubuntu_codename() {
local mint_codename="$1"
local mint_version="$2"
# Linux Mint codename -> Ubuntu codename mapping
case "${mint_codename,,}" in # ${,,} converts to lowercase
# Mint 22.x -> Ubuntu 24.04 noble
zara|zena) echo "noble" ;;
# Mint 21.x -> Ubuntu 22.04 jammy
virginia|vera|vanessa|victoria) echo "jammy" ;;
# Mint 20.x -> Ubuntu 20.04 focal
una|uma|ulyssa|ulyana) echo "focal" ;;
# Mint 19.x -> Ubuntu 18.04 bionic
tina|tessa|tara|tricia) echo "bionic" ;;
*)
# Try to derive from version number
case "${mint_version%%.*}" in
22) echo "noble" ;;
21) echo "jammy" ;;
20) echo "focal" ;;
19) echo "bionic" ;;
*) echo "" ;;
esac
;;
esac
}
# Fallback mapping for LMDE versions to Debian codenames
get_lmde_debian_codename() {
local lmde_codename="$1"
local lmde_version="$2"
case "${lmde_codename,,}" in
faye) echo "bookworm" ;; # LMDE 6
elsie) echo "bullseye" ;; # LMDE 5
debbie) echo "buster" ;; # LMDE 4
cindy) echo "stretch" ;; # LMDE 3
*)
case "${lmde_version%%.*}" in
6) echo "bookworm" ;;
5) echo "bullseye" ;;
4) echo "buster" ;;
*) echo "" ;;
esac
;;
esac
}
# Determine the base distribution (debian or ubuntu) and appropriate codename
detect_docker_repo_settings() {
local base_distro=""
local codename=""
case "$DISTRO_ID" in
debian)
base_distro="debian"
codename="$VERSION_CODENAME"
;;
ubuntu)
base_distro="ubuntu"
codename="${UBUNTU_CODENAME:-$VERSION_CODENAME}"
;;
kubuntu|lubuntu|xubuntu|pop|neon|elementary|zorin)
base_distro="ubuntu"
codename="${UBUNTU_CODENAME:-$VERSION_CODENAME}"
;;
linuxmint)
# Determine if Ubuntu-based or Debian-based (LMDE)
# LMDE has ID_LIKE=debian (not "ubuntu debian")
if [[ "$DISTRO_ID_LIKE" == "debian" ]] && [[ "$DISTRO_ID_LIKE" != *"ubuntu"* ]]; then
# This is LMDE (Debian-based)
base_distro="debian"
if [[ -n "$DEBIAN_CODENAME" ]]; then
codename="$DEBIAN_CODENAME"
else
codename=$(get_lmde_debian_codename "$VERSION_CODENAME" "$DISTRO_VERSION")
if [[ -n "$codename" ]]; then
log_info "Mapped LMDE '$VERSION_CODENAME' to Debian '$codename'"
fi
fi
else
# Ubuntu-based Linux Mint
base_distro="ubuntu"
if [[ -n "$UBUNTU_CODENAME" ]]; then
codename="$UBUNTU_CODENAME"
else
# Fallback to mapping table
codename=$(get_mint_ubuntu_codename "$VERSION_CODENAME" "$DISTRO_VERSION")
if [[ -n "$codename" ]]; then
log_info "Mapped Linux Mint '$VERSION_CODENAME' to Ubuntu '$codename'"
fi
fi
fi
;;
*)
# Try to detect from ID_LIKE
if [[ "$DISTRO_ID_LIKE" == *"ubuntu"* ]]; then
base_distro="ubuntu"
codename="${UBUNTU_CODENAME:-$VERSION_CODENAME}"
elif [[ "$DISTRO_ID_LIKE" == *"debian"* ]]; then
base_distro="debian"
codename="$VERSION_CODENAME"
else
log_error "Unsupported distribution: $DISTRO_ID (ID_LIKE: $DISTRO_ID_LIKE)"
log_error "This script supports Debian, Ubuntu, Linux Mint, and LMDE."
exit 1
fi
;;
esac
# Validate we have a codename
if [[ -z "$codename" ]]; then
log_error "Could not determine distribution codename."
log_error "VERSION_CODENAME=$VERSION_CODENAME"
log_error "UBUNTU_CODENAME=$UBUNTU_CODENAME"
log_error "DEBIAN_CODENAME=$DEBIAN_CODENAME"
log_error "Please specify the Ubuntu/Debian codename manually or update this script."
exit 1
fi
# Export for use in rest of script
DOCKER_BASE_DISTRO="$base_distro"
DOCKER_CODENAME="$codename"
DOCKER_REPO_URL="https://download.docker.com/linux/${base_distro}"
DOCKER_GPG_URL="https://download.docker.com/linux/${base_distro}/gpg"
}
detect_docker_repo_settings
log_info "=============================================="
log_info "Docker Engine Installation"
log_info "=============================================="
log_info "Detected OS: $DISTRO_NAME $DISTRO_VERSION ($VERSION_CODENAME)"
log_info "Base distribution: $DOCKER_BASE_DISTRO"
log_info "Target codename: $DOCKER_CODENAME"
log_info "Docker repo URL: $DOCKER_REPO_URL"
log_info "=============================================="
echo ""
# =============================================================================
# Check if Docker is already installed and working
# =============================================================================
DOCKER_ALREADY_INSTALLED=false
if command -v docker &>/dev/null; then
if docker version &>/dev/null; then
DOCKER_ALREADY_INSTALLED=true
INSTALLED_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null || echo "unknown")
log_info "Docker Engine is already installed and running (version: $INSTALLED_VERSION)"
else
log_warn "Docker command exists but daemon is not responding. Will attempt repair."
fi
fi
if [[ "$DOCKER_ALREADY_INSTALLED" == "true" ]]; then
log_info "Skipping installation steps. Checking user group membership..."
else
# Step 0: Clean up any existing broken Docker repository configuration
# This must happen BEFORE any apt-get update to avoid errors from previous failed installs
if [[ -f /etc/apt/sources.list.d/docker.list ]]; then
log_warn "Removing existing /etc/apt/sources.list.d/docker.list (may be misconfigured)"
rm -f /etc/apt/sources.list.d/docker.list
fi
# Also check for deb822 format (.sources files)
if [[ -f /etc/apt/sources.list.d/docker.sources ]]; then
log_warn "Removing existing /etc/apt/sources.list.d/docker.sources (may be misconfigured)"
rm -f /etc/apt/sources.list.d/docker.sources
fi
# Step 1: Remove conflicting packages
log_info "Removing any conflicting packages..."
CONFLICTING_PKGS=(
docker.io
docker-doc
docker-compose
docker-compose-v2
podman-docker
containerd
runc
)
for pkg in "${CONFLICTING_PKGS[@]}"; do
if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "^install ok installed$"; then
log_info "Removing conflicting package: $pkg"
apt-get remove -y "$pkg" || true
fi
done
# Step 2: Update package index and install prerequisites
log_info "Installing prerequisites..."
apt-get update
apt-get install -y ca-certificates curl
# Step 3: Set up Docker's official GPG key
log_info "Adding Docker's official GPG key..."
install -m 0755 -d /etc/apt/keyrings
curl -fsSL "$DOCKER_GPG_URL" -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Step 4: Add Docker repository to apt sources
log_info "Adding Docker repository..."
ARCH=$(dpkg --print-architecture)
echo "deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.asc] ${DOCKER_REPO_URL} ${DOCKER_CODENAME} stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
log_info "Repository configured: ${DOCKER_REPO_URL} ${DOCKER_CODENAME} stable"
# Step 5: Install Docker Engine packages
log_info "Installing Docker Engine..."
apt-get update
apt-get install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
# Step 6: Enable and start Docker service
log_info "Enabling and starting Docker service..."
systemctl enable docker.service
systemctl enable containerd.service
systemctl start docker.service
systemctl start containerd.service
# Step 7: Create docker group if it doesn't exist
if ! getent group docker &>/dev/null; then
log_info "Creating docker group..."
groupadd docker
fi
fi # End of "if not already installed" block
# =============================================================================
# User group management (always runs, even if Docker was already installed)
# =============================================================================
# Step 8: Add users to docker group (only if not already members)
USERS_ADDED=()
USERS_ALREADY_IN_GROUP=()
if [[ ${#DOCKER_USERS[@]} -gt 0 ]]; then
for user in "${DOCKER_USERS[@]}"; do
if ! id "$user" &>/dev/null; then
log_warn "User '$user' does not exist, skipping."
continue
fi
# Check if user is already in docker group
if id -nG "$user" | grep -qw docker; then
log_info "User '$user' is already in docker group, skipping."
USERS_ALREADY_IN_GROUP+=("$user")
continue
fi
# Add user to docker group
log_info "Adding user '$user' to docker group..."
if usermod -aG docker "$user"; then
# Verify the user was added
if id -nG "$user" | grep -qw docker; then
log_info "Successfully added '$user' to docker group."
USERS_ADDED+=("$user")
else
log_warn "Command succeeded but '$user' not showing in docker group yet."
USERS_ADDED+=("$user")
fi
else
log_error "Failed to add '$user' to docker group."
fi
done
else
log_warn "No users were specified for docker group."
log_warn "To add yourself manually, run: sudo usermod -aG docker \$USER"
fi
# Step 9: Verify installation
log_info "Verifying Docker installation..."
if docker --version; then
log_info "Docker CLI: OK"
else
log_error "Docker CLI verification failed."
exit 1
fi
if docker compose version &>/dev/null; then
log_info "Docker Compose plugin: OK"
fi
# Run hello-world as verification (skip if this is a reentrant run and Docker was already working)
if [[ "$DOCKER_ALREADY_INSTALLED" != "true" ]]; then
log_info "Running hello-world container to verify Docker Engine..."
if docker run --rm hello-world; then
log_info "Docker Engine is working correctly!"
else
log_error "Docker hello-world test failed."
exit 1
fi
else
log_info "Docker daemon: OK (skipped hello-world test)"
fi
# Final instructions
echo ""
log_info "=============================================="
if [[ "$DOCKER_ALREADY_INSTALLED" == "true" ]]; then
log_info "Docker Engine was already installed."
else
log_info "Docker Engine installation complete!"
fi
log_info "=============================================="
echo ""
# Report on user group changes
if [[ ${#USERS_ADDED[@]} -gt 0 ]]; then
log_info "Users added to docker group: ${USERS_ADDED[*]}"
echo ""
log_warn "IMPORTANT: For non-root docker access to take effect, each user must either:"
echo " 1. Log out and log back in, OR"
echo " 2. Run: newgrp docker"
echo ""
elif [[ ${#USERS_ALREADY_IN_GROUP[@]} -gt 0 ]]; then
log_info "Users already in docker group: ${USERS_ALREADY_IN_GROUP[*]}"
log_info "No changes needed for group membership."
echo ""
fi
log_info "Installed versions:"
docker --version
docker compose version
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment