How to Install and Use Docker on Debian 12 (Step-by-Step)

Install Docker on Debian 12 with Docker’s official APT repository, Compose v2, non-root access, container tests, and practical troubleshooting steps.
A polished 3D server room with glowing Docker containers and deep-red accents on a midnight-blue background.

Install Docker on Debian 12 with the official Docker Engine packages, Compose v2, and a non-root administration setup. In about ten minutes, you will pull a Bookworm image, run a container, inspect it, and clean it up safely.

Prerequisites

  • A 64-bit Debian GNU/Linux 12 (Bookworm) VPS with a current package list.
  • SSH access as root, or a normal account with working sudo privileges.
  • At least 2 GB of RAM and 10 GB of free disk space for Docker, images, logs, and test containers.
  • Outbound HTTPS access to Docker’s package repository and Docker Hub.
  • A VPSLake Linux VPS if you need a Debian server with root access.

The commands use sudo for a normal SSH account. If you are already logged in as root, remove sudo from those commands.

Step 1: Confirm Bookworm and refresh APT

Confirm the release before adding a third-party repository so APT cannot accidentally mix packages from another Debian suite.

grep -E '^(PRETTY_NAME|VERSION_CODENAME)=' /etc/os-release
dpkg --print-architecture
sudo apt update

The release name should be bookworm; amd64 is common on VPS plans, although Docker also publishes packages for several other Debian architectures.

PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
VERSION_CODENAME=bookworm
amd64

Resolve any DNS, mirror, or expired-release errors from apt update before continuing. A Docker repository cannot repair a broken Debian package source.

Step 2: Add Docker’s signed Bookworm repository

The keyring and deb822 source file let APT verify Docker’s repository without the deprecated apt-key command.

Install the download tools and create the dedicated keyring directory:

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

Create the repository entry. The shell substitutions read the host’s actual Debian codename and CPU architecture, so the file stays correct on both common amd64 and arm64 VPSs.

sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update

Check that APT sees a Docker candidate from the Bookworm repository:

apt-cache policy docker-ce

The candidate version changes over time, but its source should contain download.docker.com and bookworm.

docker-ce:
  Candidate: 5:29.x.y-1~debian.12~bookworm
  500 https://download.docker.com/linux/debian bookworm/stable amd64 Packages

For the repository layout and supported architectures, see Docker’s Debian installation guide.

Step 3: Install Docker Engine and Compose

These packages install the daemon, command-line client, container runtime, Buildx builder, and the current Compose v2 plugin as one compatible set.

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

Verify that systemd has started the daemon and that both the CLI and Compose plugin are available:

sudo systemctl is-active docker
sudo systemctl is-enabled docker
docker --version
docker compose version

Typical output looks like this; exact version numbers will change with Docker updates.

active
enabled
Docker version 29.x.y, build ...
Docker Compose version v2.x.y

Step 4: Give a trusted account Docker access

Docker commands use a root-owned Unix socket by default. Adding an account to the docker group removes repeated sudo prompts, but it also gives that account root-equivalent control over the host.

Run the following as the account that should manage containers, replacing the uppercase placeholder with its Linux username:

DOCKER_USER="YOUR_LINUX_USER"
sudo groupadd --force docker
sudo usermod -aG docker "$DOCKER_USER"

Log out of SSH completely and reconnect as that user. A new shell is required for the group change to appear. Then check the group list and query the daemon:

id -nG
docker info --format 'Server {{.ServerVersion}}'

The first result should include docker, and the second should print the installed Engine version. Until you reconnect, use sudo docker ... or run newgrp docker for a temporary group-aware shell.

Step 5: Pull and run a Debian container

An explicitly tagged image makes the test repeatable and demonstrates the difference between an image (the template) and a container (a running instance of it).

docker pull debian:bookworm-slim
docker run --rm debian:bookworm-slim cat /etc/debian_version

The pull downloads the image if it is not cached. The command inside the short-lived container prints a Debian 12 version and --rm removes the stopped container automatically.

Status: Downloaded newer image for debian:bookworm-slim
12.x

The digest and layer messages vary. The important result is a 12. version line and an exit status of zero.

Step 6: Run, inspect, and remove a web container

This example keeps the published port on loopback, so it is useful for testing without exposing a new service to the Internet.

docker run -d --name demo-nginx --publish 127.0.0.1:8080:80 nginx:alpine
docker ps --filter name=demo-nginx --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
curl --head http://127.0.0.1:8080

You should see a running container and an HTTP success response. The 127.0.0.1 on the left side of the port mapping means only the VPS itself can reach port 8080; no public firewall opening is needed for this demonstration.

NAMES        STATUS        PORTS
demo-nginx   Up ...        127.0.0.1:8080->80/tcp
HTTP/1.1 200 OK

View recent output, stop the workload, and remove its container record when finished:

docker logs --tail 10 demo-nginx
docker stop demo-nginx
docker rm demo-nginx

Stopping ends the process while preserving the container. Removing it deletes that container’s writable layer; the nginx:alpine image remains available for another run.

Verify Docker on Debian 12

Run this compact check after reconnecting as your Docker administrator. It confirms boot-time startup, client/server communication, Compose, and the image cache.

sudo systemctl is-active docker
docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
docker compose version
docker image ls debian:bookworm-slim

Expected results include active, matching client/server versions, a Compose v2 version, and a row for debian:bookworm-slim. Docker is ready for an application image or a Compose project.

Troubleshooting

E: Package 'docker-ce' has no installation candidate

APT has not loaded Docker’s Bookworm index, or the source file contains the wrong suite or architecture. Inspect the entry and refresh the package cache:

cat /etc/apt/sources.list.d/docker.sources
dpkg --print-architecture
sudo apt update
apt-cache policy docker-ce

The file should say Suites: bookworm, and its architecture should match dpkg --print-architecture. Fix every apt update error before retrying the installation.

NO_PUBKEY or a repository signature error

APT cannot read the Docker key or the file permissions prevent the _apt user from using it. Download the key again, make it readable, and refresh metadata:

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 apt update

If the message mentions an invalid date or an expired signature, check the VPS clock with date -u and repair time synchronization before trusting package metadata.

permission denied while trying to connect to the Docker daemon socket

Your current SSH session was opened before the docker group was added. Disconnect and reconnect as YOUR_LINUX_USER, or run newgrp docker; then confirm with id -nG. sudo docker info is a temporary workaround for an account that already has sudo.

Cannot connect to the Docker daemon

The service is stopped or failed during startup. Read the unit state and recent journal entries before restarting it:

sudo systemctl status docker --no-pager
sudo journalctl -u docker -n 50 --no-pager
sudo systemctl restart docker

If it fails again, check for an existing docker.io, containerd, or runc installation and review any custom file under /etc/docker/. Do not remove /var/lib/docker unless you intentionally want to delete existing images, containers, and volumes.

A published container port is reachable from the wrong place

Publishing with -p 8080:80 binds the host port on all interfaces, while the example’s -p 127.0.0.1:8080:80 restricts it to the VPS. Docker’s port rules can also bypass ordinary UFW expectations, so read the official Docker firewall guidance before publishing a public service. Use a reverse proxy with TLS and an explicit host firewall policy for production traffic.

Hardening

  • Keep the Docker daemon on its Unix socket. Do not expose an unauthenticated TCP socket on port 2375; remote Docker access should use properly protected TLS and a narrow firewall policy.
  • Treat membership in the docker group as administrative access. Use rootless mode if your workload needs stronger separation from the host.
  • Pin important workloads to reviewed image tags or digests, update them deliberately, and remove unused images only after checking what will be deleted.
  • Keep application ports on 127.0.0.1 unless they must be public. When you publish a public port, place restrictions in Docker’s packet-filtering chain rather than relying on an ordinary host input rule.

FAQ

Should I install Debian’s docker.io package instead?

Debian’s package can be suitable when you want distribution-maintained versions, but this guide uses Docker’s official APT repository for the Docker Engine package set and Compose plugin. Do not mix docker.io and docker-ce dependencies on the same host without first resolving the package conflict.

Why does Docker require containerd.io?

The Engine relies on containerd for core image and container lifecycle work. Installing Docker’s bundled containerd.io package keeps that runtime aligned with the Engine and avoids an accidental mix with an incompatible distribution package.

What is the difference between an image and a container?

An image is an immutable package of filesystem layers and startup metadata. A container is a runtime instance created from that image, with its own process, writable layer, networks, and optional volumes.

How do I make a container available on the Internet?

Publish only the required host port and restrict it with a deliberate firewall policy; do not assume a normal UFW allow or deny rule will control Docker-forwarded traffic. For a web application, a TLS-enabled reverse proxy is usually easier to secure than exposing the application container directly.

Samrat Ghosh
Written by

Samrat Ghosh

Founder & Infrastructure Engineer

Samrat Ghosh is the founder of VPSLake, where he builds and runs the remote desktop and VPS hosting infrastructure the company is built on. He writes hands-on guides about RDP, Windows Server, VPS management and secure remote access - the practical documentation he wishes had…

Previous Article

How to Install and Use Docker on Debian 11 (Step-by-Step)

Next Article

Windows 10 End of Support: What It Means for RDP

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨