Install Docker and Docker Compose
Install Docker Engine and the Compose plugin from Docker's repository on Ubuntu, Debian, AlmaLinux, or Rocky, and run a first Compose project safely.
Most of the self-hosted software in our Solutions guides, from n8n and Nextcloud to the AI agent gateways, ships as a Docker image with a Compose file. Installing Docker properly (from Docker's own repository, not the distribution's older package) takes five minutes and saves hours later.
Ubuntu and Debian#
# prerequisites and Docker's signing key
sudo apt update && sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# engine, CLI, and the Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
AlmaLinux and Rocky Linux#
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
Finish up on either#
sudo systemctl enable --now docker
sudo usermod -aG docker $USER # run docker without sudo; log out and in again
docker run --rm hello-world
docker compose version
Membership of the docker group is equivalent to root on the server. Grant it to your own admin user, not to accounts that only need to deploy one app.
A first Compose project#
Keep each application in its own directory with a compose.yaml, and keep data in named volumes or bind-mounted folders you know how to back up:
# /opt/whoami/compose.yaml
services:
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
ports:
- "127.0.0.1:8080:80" # bind to localhost; the reverse proxy exposes it
cd /opt/whoami && docker compose up -d
docker compose ps
curl -s http://127.0.0.1:8080/ | head -3
docker compose logs -f # Ctrl+C to stop following
restart: unless-stopped brings containers back after a reboot without any systemd work. Publishing ports on 127.0.0.1 keeps the app off the public internet until a reverse proxy puts it there with TLS.
Firewall note#
Docker manipulates iptables directly and can bypass UFW rules for ports published on 0.0.0.0. Binding to 127.0.0.1 as above sidesteps the issue for web apps; for anything that genuinely must listen publicly, publish it deliberately and rely on your firewall plus the container's own auth.
Housekeeping#
docker compose pull && docker compose up -d # update an app to the latest images
docker system prune # remove stopped containers and unused networks
docker system df # what is using disk
Next: put it behind HTTPS with Reverse Proxy with Free TLS, and back up its data with Back Up a Dockerised App.
