Debian is a favourite base for Docker hosts: small, stable and predictable. Debian’s own docker.io package works, but Docker’s official repository gives you current releases plus the Compose and Buildx plugins. Here’s the clean way to set it up on Debian 12 (bookworm) and Debian 13 (trixie).

Run the commands as root, or prefix them with sudo if your user has it.

1. Remove old or conflicting packages

for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do
  apt-get remove -y $pkg
done

It’s fine if none of them are installed.

2. Add the Docker repository

apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  > /etc/apt/sources.list.d/docker.list

apt-get update

Note the URL path is /linux/debian, not /linux/ubuntu — mixing them up is the most common installation error on Debian. If Docker hasn’t published packages for a brand-new Debian release yet, apt-get update reports a missing Release file; wait for upstream support rather than pointing at the previous codename.

3. Install

apt-get install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

Verify:

systemctl is-active docker
docker run --rm hello-world
docker compose version

4. Let a regular user run Docker

usermod -aG docker youruser

Log out and back in for the group to apply. Remember that the docker group grants root-equivalent access to the host — add only administrators.

5. Limit container log size

Debian VPS images often have modest disks, and container logs grow forever by default. Create /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
systemctl restart docker

New containers get the limit; recreate existing ones to apply it.

6. Firewalls: Docker vs UFW and nftables

Debian 12 and 13 use nftables under the hood, and Docker programs its own rules (through the iptables-nft compatibility layer). The effect matters for security:

  • A port published with -p 8080:80 is reachable from outside even if UFW or your own nftables rules block 8080, because Docker inserts its rules first.
  • Publish internal services on localhost only — -p 127.0.0.1:8080:80 — and expose them through a reverse proxy.
  • To filter traffic to containers, add rules to the DOCKER-USER chain, which Docker leaves for you and evaluates before its own rules.

Example: allow access to published container ports only from one office IP:

iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.10 -j DROP

(Replace eth0 with your public interface from ip -br addr.) Save such rules with iptables-persistent or a systemd unit, since they don’t survive a reboot otherwise.

7. Where Docker keeps data

Everything — images, containers, volumes — lives under /var/lib/docker. If your VPS has a separate, larger data disk, move it there before you pull many images:

{
  "data-root": "/srv/docker"
}

Stop Docker, copy the old directory with rsync -aP /var/lib/docker/ /srv/docker/, add the setting to daemon.json, then start Docker again.

Keeping Docker up to date

Docker updates arrive with normal system updates:

apt-get update && apt-get upgrade

If you use unattended upgrades, note that by default only the Debian security archive is upgraded automatically; Docker’s repository is not included unless you add it to the allowed origins. Many admins prefer updating Docker by hand, since a daemon restart restarts all containers.

Troubleshooting

  • E: Package 'docker-ce' has no installation candidate — the repository line is wrong (often Ubuntu URL or a typo in the codename). Check /etc/apt/sources.list.d/docker.list.
  • docker.service fails to start — run journalctl -u docker -n 50. Invalid JSON in daemon.json is the usual culprit; validate it with python3 -m json.tool /etc/docker/daemon.json.
  • Containers have no internet access — check that IP forwarding wasn’t disabled by a hardening script: sysctl net.ipv4.ip_forward should be 1 while Docker runs.

Prefer Ubuntu? The steps are almost identical: install Docker on Ubuntu. Our Debian VPS images are minimal installs, ideal as Docker hosts.

  • Docker
  • Debian
  • Containers