#!/usr/bin/env bash set -Eeuo pipefail SCRIPT_NAME="$(basename "$0")" DEFAULT_USER="${SUDO_USER:-${USER:-}}" DOCKER_USER="" ADD_DOCKER_GROUP="ask" INSTALL_PORTAINER="no" PORTAINER_HTTPS_PORT="9443" PORTAINER_EDGE_PORT="8000" PORTAINER_IMAGE="portainer/portainer-ce:lts" usage() { cat <<'USAGE' Install Docker Engine and Docker Compose plugin on a fresh Debian/Ubuntu server. Optionally install Portainer CE after Docker is running. Usage: ./install-docker.sh [options] curl -fsSL /install-docker.sh | bash -s -- [options] Options: --user USER Add USER to the docker group after install. --current-user Add the invoking user to the docker group. --no-docker-user Do not add anyone to the docker group. --portainer Install or start Portainer CE after Docker install. --portainer-port PORT Host port for Portainer HTTPS UI. Default: 9443. --portainer-edge-port PORT Host port for Portainer Edge agent tunnel. Default: 8000. --portainer-image IMAGE Portainer image to run. Default: portainer/portainer-ce:lts. -y, --yes Non-interactive defaults. Installs Docker and skips docker group unless --user/--current-user is also supplied. -h, --help Show this help. Examples: curl -fsSL https://gitea.wheelytho.com/Wheelz/Docker-Install/raw/branch/main/install-docker.sh | bash curl -fsSL https://gitea.wheelytho.com/Wheelz/Docker-Install/raw/branch/main/install-docker.sh | bash -s -- --user obin curl -fsSL https://gitea.wheelytho.com/Wheelz/Docker-Install/raw/branch/main/install-docker.sh | bash -s -- --current-user curl -fsSL https://gitea.wheelytho.com/Wheelz/Docker-Install/raw/branch/main/install-docker.sh | bash -s -- --current-user --portainer USAGE } log() { printf '\n[Docker-Install] %s\n' "$*"; } warn() { printf '\n[Docker-Install WARNING] %s\n' "$*" >&2; } fail() { printf '\n[Docker-Install ERROR] %s\n' "$*" >&2; exit 1; } require_port_number() { local name="$1" local value="$2" [[ "$value" =~ ^[0-9]+$ ]] || fail "$name must be a number. Got: $value" (( value >= 1 && value <= 65535 )) || fail "$name must be between 1 and 65535. Got: $value" } install_portainer() { log "Installing Portainer CE" require_port_number "--portainer-port" "$PORTAINER_HTTPS_PORT" require_port_number "--portainer-edge-port" "$PORTAINER_EDGE_PORT" $SUDO docker volume create portainer_data >/dev/null if $SUDO docker container inspect portainer >/dev/null 2>&1; then if [[ "$($SUDO docker inspect -f '{{.State.Running}}' portainer 2>/dev/null)" == "true" ]]; then log "Portainer container already exists and is running" else log "Portainer container already exists; starting it" $SUDO docker start portainer >/dev/null fi else $SUDO docker run -d \ --name portainer \ --restart=always \ -p "${PORTAINER_EDGE_PORT}:8000" \ -p "${PORTAINER_HTTPS_PORT}:9443" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ "$PORTAINER_IMAGE" >/dev/null fi local host_ip host_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" [[ -n "$host_ip" ]] || host_ip="" log "Portainer is available at: https://${host_ip}:${PORTAINER_HTTPS_PORT}" warn "Portainer uses a self-signed certificate by default, so your browser may show a warning." } while [[ $# -gt 0 ]]; do case "$1" in --user) [[ $# -ge 2 ]] || fail "--user requires a username" DOCKER_USER="$2" ADD_DOCKER_GROUP="yes" shift 2 ;; --current-user) [[ -n "$DEFAULT_USER" && "$DEFAULT_USER" != "root" ]] || fail "Could not determine a non-root invoking user. Use --user USER instead." DOCKER_USER="$DEFAULT_USER" ADD_DOCKER_GROUP="yes" shift ;; --no-docker-user) ADD_DOCKER_GROUP="no" DOCKER_USER="" shift ;; --portainer) INSTALL_PORTAINER="yes" shift ;; --portainer-port) [[ $# -ge 2 ]] || fail "--portainer-port requires a port number" PORTAINER_HTTPS_PORT="$2" shift 2 ;; --portainer-edge-port) [[ $# -ge 2 ]] || fail "--portainer-edge-port requires a port number" PORTAINER_EDGE_PORT="$2" shift 2 ;; --portainer-image) [[ $# -ge 2 ]] || fail "--portainer-image requires an image name" PORTAINER_IMAGE="$2" shift 2 ;; -y|--yes) [[ "$ADD_DOCKER_GROUP" == "ask" ]] && ADD_DOCKER_GROUP="no" shift ;; -h|--help) usage exit 0 ;; *) fail "Unknown option: $1. Run with --help for usage." ;; esac done if [[ "${EUID}" -ne 0 ]]; then if command -v sudo >/dev/null 2>&1; then SUDO="sudo" else fail "This installer needs root privileges. Re-run as root or install sudo." fi else SUDO="" fi if [[ ! -r /etc/os-release ]]; then fail "Cannot read /etc/os-release. This installer supports Debian/Ubuntu style systems." fi # shellcheck disable=SC1091 . /etc/os-release OS_ID="${ID:-}" OS_CODENAME="${VERSION_CODENAME:-}" case "$OS_ID" in ubuntu|debian) ;; *) fail "Unsupported OS '${OS_ID:-unknown}'. This script currently supports Ubuntu and Debian." ;; esac if [[ -z "$OS_CODENAME" ]] && command -v lsb_release >/dev/null 2>&1; then OS_CODENAME="$(lsb_release -cs)" fi [[ -n "$OS_CODENAME" ]] || fail "Could not determine OS codename for Docker apt repo." if [[ "$ADD_DOCKER_GROUP" == "ask" ]]; then if [[ -n "$DEFAULT_USER" && "$DEFAULT_USER" != "root" && -t 0 ]]; then read -r -p "Add '${DEFAULT_USER}' to the docker group for passwordless docker commands? [y/N]: " reply case "$reply" in [Yy]*) ADD_DOCKER_GROUP="yes"; DOCKER_USER="$DEFAULT_USER" ;; *) ADD_DOCKER_GROUP="no" ;; esac else ADD_DOCKER_GROUP="no" fi fi if [[ "$ADD_DOCKER_GROUP" == "yes" ]]; then [[ -n "$DOCKER_USER" ]] || fail "Docker group add requested but no user was selected. Use --user USER." id "$DOCKER_USER" >/dev/null 2>&1 || fail "User '$DOCKER_USER' does not exist. Create the user first or choose a different username." fi log "Installing prerequisites" $SUDO apt-get update $SUDO apt-get install -y ca-certificates curl gnupg log "Adding Docker's official apt repository for ${OS_ID} ${OS_CODENAME}" $SUDO install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/${OS_ID}/gpg" | $SUDO gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg $SUDO chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${OS_ID} ${OS_CODENAME} stable" \ | $SUDO tee /etc/apt/sources.list.d/docker.list >/dev/null log "Installing Docker Engine and Compose plugin" $SUDO apt-get update $SUDO apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin log "Enabling and starting Docker" $SUDO systemctl enable --now docker if [[ "$ADD_DOCKER_GROUP" == "yes" ]]; then log "Adding '${DOCKER_USER}' to the docker group" $SUDO groupadd -f docker $SUDO usermod -aG docker "$DOCKER_USER" fi log "Verifying install" $SUDO docker --version docker compose version || $SUDO docker compose version $SUDO docker run --rm hello-world >/dev/null if [[ "$INSTALL_PORTAINER" == "yes" ]]; then install_portainer fi cat < newgrp docker EOF fi if [[ "$INSTALL_PORTAINER" == "yes" ]]; then cat <:${PORTAINER_HTTPS_PORT} EOF else cat <