Created
November 27, 2025 14:01
-
-
Save matthewadams/ebfaf1c37ab0e5a0fca9bc73f7a1d863 to your computer and use it in GitHub Desktop.
Automated docker engine install script for Debian that also supports Linux Mint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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