Install Docker on Debian 12 using Docker Engine, Compose v2, and a non-root command workflow. In roughly ten minutes, you can pull an image, run a container, and confirm that the daemon will start after a reboot.
Debian 12 is Bookworm, so this guide uses Docker’s official Bookworm-compatible APT repository instead of Debian’s older docker.io package. The test binds only to localhost, which lets you validate networking without placing an unfinished service on the internet.
Prerequisites
- A 64-bit Debian 12 (Bookworm) VPS with at least 2 GB of RAM and 10 GB of free disk space.
- SSH access as
root, or a normal Linux account with workingsudoprivileges. - Outbound HTTPS access to
download.docker.comand Docker Hub so APT can download packages and Docker can pull images. - No domain is needed for the installation or local test. You need a domain later only if you put a public web application behind a TLS reverse proxy.
- A VPSLake Linux VPS if you need a Debian server with root access for containers.
The commands below assume a sudo-enabled account. If you are logged in as root, remove sudo; for the non-root setup, replace $USER with the existing account that should manage Docker.
Step 1: Confirm Debian 12 and refresh APT
Checking the release first prevents you from adding a repository for another Debian codename or architecture.
grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME)=' /etc/os-release
dpkg --print-architecture
sudo apt update
sudo apt full-upgrade -y
The release should identify Bookworm and the architecture should normally be amd64 on a VPS:
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
VERSION_ID="12"
VERSION_CODENAME=bookworm
amd64
The minor release and package counts will vary. Resolve any DNS, mirror, or interrupted-package error before continuing.
Step 2: Remove packages that conflict with Docker Engine
Debian’s docker.io package and separately installed container runtimes can conflict with the versions Docker ships in its repository.
On a fresh VPS, run:
sudo apt remove -y docker.io docker-compose docker-doc docker-buildx podman-docker containerd runc
APT may report that some packages are not installed. This command does not remove images, containers, volumes, or networks already stored under /var/lib/docker/; inspect existing workloads before changing a server that already runs containers.
Step 3: Add Docker’s signed Bookworm repository
The repository gives you Docker Engine, its CLI, containerd, Buildx, and the maintained Compose v2 plugin as one compatible package set.
Install the certificate and download tools, store Docker’s key in the dedicated APT keyring directory, then create a deb822 source file:
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: bookworm
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
Confirm APT can see Docker packages for Bookworm before installing them:
apt-cache policy docker-ce
The candidate should come from https://download.docker.com/linux/debian and include a Debian 12/Bookworm package build. Docker’s Debian installation guide documents this repository method and supported architectures.
Step 4: Install Docker Engine and Compose v2
This package set installs the daemon, CLI, container runtime, image builder, and the docker compose subcommand.
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
Check that systemd is running Docker and that the command-line tools can reach the daemon:
sudo systemctl is-active docker
sudo systemctl is-enabled docker
sudo docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
sudo docker compose version
Expected output has an active, enabled service and version strings. Exact patch numbers change as Docker publishes releases:
active
enabled
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y
Step 5: Run Docker’s built-in smoke test
The hello-world image checks the complete path from the CLI to the daemon, registry, image cache, container creation, and process startup.
sudo docker run --rm hello-world
Near the end, look for the following confirmation:
Hello from Docker!
Your installation appears to be working correctly.
The test container exits on its own, and --rm removes its stopped container record. Its small image remains cached until you remove it.
Step 6: Allow one account to use Docker without sudo
Docker’s Unix socket is root-owned by default. The docker group removes repeated sudo prompts, but membership is administrative access because a Docker user can effectively control the host.
Run this as the account that should operate containers:
sudo groupadd --force docker
sudo usermod -aG docker "$USER"
Sign out of SSH completely and reconnect, or activate the new group in the current shell with newgrp docker. Then check the account’s access:
id -nG
docker info --format 'Server {{.ServerVersion}}'
The group list should contain docker, and the second command should print the Engine version. Keep using sudo docker until the new group is loaded if you prefer not to start a child shell.
Step 7: Test container networking on loopback
A temporary NGINX container verifies port publishing while binding only to the VPS itself, so the test does not require a firewall rule or a public DNS record.
docker run -d --name debian12-docker-check --publish 127.0.0.1:18080:80 nginx:alpine
docker ps --filter name=debian12-docker-check --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
curl --head --max-time 10 http://127.0.0.1:18080
You should see a running container and an HTTP success response:
NAMES STATUS PORTS
debian12-docker-check Up ... 127.0.0.1:18080->80/tcp
HTTP/1.1 200 OK
Remove the disposable test when the response is confirmed:
docker stop debian12-docker-check
docker rm debian12-docker-check
Verify Docker on Debian 12
Use this final check to confirm startup, daemon access, Compose, and the local storage driver:
systemctl is-active docker
systemctl is-enabled docker
docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
docker compose version
docker info --format 'Containers {{.Containers}} | Images {{.Images}} | Storage {{.Driver}}'
Successful output includes active, enabled, client/server versions, a Compose v2 version, and commonly overlay2 as the storage driver:
active
enabled
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y
Containers 0 | Images 2 | Storage overlay2
For Docker’s interaction with published ports and host firewall rules, read the official packet-filtering and firewall guidance.
Troubleshooting
E: Package 'docker-ce' has no installation candidate
APT has not loaded Docker’s repository, or the source file uses a suite that does not match Debian 12. Check the release and source, then refresh the package lists:
grep -E '^(VERSION_ID|VERSION_CODENAME)=' /etc/os-release
grep -E '^(Types|URIs|Suites|Components|Architectures|Signed-By):' /etc/apt/sources.list.d/docker.sources
sudo apt update
apt-cache policy docker-ce
The suite must be bookworm, and the policy output should show a candidate from download.docker.com. Recreate Step 3 if the file is missing or has a different suite.
NO_PUBKEY or a signature verification error
APT cannot read the key used by the Docker source. Check that /etc/apt/keyrings/docker.asc exists and is readable, then run the key download and chmod commands from Step 3 again before retrying sudo apt update.
permission denied while trying to connect to the Docker daemon socket
Your current SSH process was created before the account joined the docker group, or the group command targeted another username. Reconnect and run id -nG; newgrp docker is a temporary current-shell fix. You can also use sudo docker info while correcting the account setup.
WARNING: Error loading config file ... permission denied
Running Docker with sudo can create a root-owned ~/.docker directory in your user’s home. If you do not need settings stored there, remove that directory; otherwise repair its ownership and permissions:
sudo chown "$USER":"$USER" "$HOME/.docker" -R
sudo chmod g+rwx "$HOME/.docker" -R
bind: address already in use during the NGINX test
Another local process owns port 18080. Find the listener and choose another loopback port, such as 28080:
sudo ss -ltnp 'sport = :18080'
docker run -d --name debian12-docker-check --publish 127.0.0.1:28080:80 nginx:alpine
curl --head --max-time 10 http://127.0.0.1:28080
Stop and remove the test container afterward as shown in Step 7.
Hardening
- Do not publish Docker’s API on TCP port 2375. Keep administration on the Unix socket unless you have deliberately configured TLS and restricted the source IPs.
- Bind internal applications to
127.0.0.1and place a TLS reverse proxy in front of them. When a service must be public, allow only its required ports and review Docker’sDOCKER-USERchain guidance; published container ports can bypass ordinaryufwfiltering. A VPSLake UFW firewall generator can help create a starting ruleset. - Treat the
dockergroup as root-equivalent access. For a stronger privilege boundary, evaluate Docker rootless mode before adding users to the group. - Monitor
/var/lib/dockerand container logs. Image layers and default JSON logs can consume disk even when the host itself looks lightly used.
FAQ
Is Docker Engine available for Debian 12?
Yes. Docker’s current Debian instructions list Bookworm 12 as a supported release and provide packages through Docker’s Debian repository. The repository is preferable here because it supplies Docker Engine and the maintained Compose plugin together.
Do I need docker-compose on Debian 12?
No. The old docker-compose package is a legacy standalone command. Install docker-compose-plugin and use docker compose with a space; this is the current Compose v2 interface.
Should I open port 18080 in UFW?
No. The example publishes 127.0.0.1:18080, so it accepts connections only from the VPS itself and needs no public firewall rule. If you later publish an application on 0.0.0.0, review Docker’s firewall behavior before assuming a normal UFW allow or deny rule protects that container.
Where does Docker store its images and containers?
The default Docker data root is /var/lib/docker, while containerd keeps runtime data under /var/lib/containerd. Make sure the filesystem has room before pulling large images, and do not prune volumes or images until you have identified which workloads still need them.