All tutorials
14 min read

How to Install Docker and Docker Compose on Debian 12 and 13

Install Docker and Docker Compose on a Debian 12 or 13 server from Docker's repository, run containers as your own user and pick which apps are public.

TL;DR:

  • To install Docker on Debian 13 or 12, add Docker's apt repository and install docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin and docker-compose-plugin.
  • The same commands installed Docker Engine 29.9.3 and Docker Compose 5.7.0 on Debian 13.7 and 12.15 in November 2026. Debian's own docker.io is 26.1.5 on Debian 13 and 20.10.24 on Debian 12.
  • The docker group gives root-level access to the server, so add only your sudo user.
  • By default, Docker publishes ports on every address of the server, and Debian adds no firewall rules of its own. Make 127.0.0.1 the default bind address.
  • Docker does not support firewall rules written with nft, and Debian's nftables service deletes Docker's rules when it starts, reloads or stops.
  • Docker keeps container logs with no size limit by default. Step 6 caps them at 3 files of 10 MB per container in /etc/docker/daemon.json.

Applies to: Debian 13.7 (Trixie) and Debian 12.15 (Bookworm) · Docker Engine 29.9.3 · Docker Compose 5.7.0 · checked November 2026

Docker Engine is the open-source runtime that builds and runs containers on a Linux server, and the Docker Compose plugin adds the docker compose command, which starts a group of containers from one compose.yaml file. This tutorial shows how to install Docker on Debian 13 (Trixie) or Debian 12 (Bookworm) from Docker's own apt repository, then covers the docker group, log limits, Debian's firewall defaults and a first Compose app. The commands are the same on both releases.

Does Docker support Debian 12 and 13?

Yes. Docker's Debian install page lists Debian Trixie 13 (stable) and Debian Bookworm 12 (oldstable) on amd64, arm64, armhf and ppc64el.

ReleaseFull Debian supportLong Term Support
Debian 13 (Trixie)Until August 9, 2028Until June 30, 2030
Debian 12 (Bookworm)Ended July 11, 2026Until June 30, 2028

The dates are from Debian's trixie and bookworm release pages.

On a server, install Docker Engine from Docker's apt repository, as this tutorial does. Docker's page also lists Docker Desktop for Linux, a desktop app that bundles Docker Engine, and the get.docker.com convenience script, which Docker recommends only for testing and development.

Prerequisites

  • A server running Debian 13 or Debian 12 (64-bit) with SSH access. Arct Cloud Linux VPS images include Debian 12 and Debian 13.
  • A non-root user with sudo privileges and SSH key login. How to Generate an SSH Key on Windows, macOS, and Linux creates the key and the sudo user.
  • Disk space for Docker and your images: on minimal systems, apt needed 506 MB (Debian 13) and 497 MB (Debian 12) for Docker and its dependencies. Docker's install page sets no RAM or disk minimum, so size the server for your containers.

Commands run as the sudo user. Replace values written in capitals, such as YOUR_IP, with your own. The examples use 198.51.100.20 as your own IP address.

Step 1: Remove old or conflicting Docker packages

Debian's own docker.io, docker-compose and related packages conflict with Docker's packages. Remove any that are installed:

sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc \
  docker-buildx podman-docker containerd runc | cut -f1)

On a server without them, apt removes nothing. The output on Debian 13 looks similar to this:

dpkg: no packages found matching docker.io
...
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0

Data in /var/lib/docker stays on disk. On Debian 13, Debian's docker-cli package can stay installed after this command. apt removes it in Step 3, when it installs Docker's docker-ce-cli in its place.

Step 2: Add Docker's repository

Download Docker's signing key into the apt keyrings directory:

sudo apt update
sudo apt install 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 source in the deb822 format, which apt reads on Debian 12 and 13. The command fills in your release codename and architecture:

sudo tee /etc/apt/sources.list.d/docker.sources <<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

The output on Debian 13 looks similar to this:

Types: deb
URIs: https://download.docker.com/linux/debian
Suites: trixie
Components: stable
Architectures: arm64
Signed-By: /etc/apt/keyrings/docker.asc

On Debian 12 Suites reads bookworm, and on an x86-64 server Architectures reads amd64. Refresh the package index:

sudo apt update

Step 3: Install Docker Engine and Compose on Debian

Install the five packages, and type Y when apt asks to continue:

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

docker-ce depends on iptables and nftables, so apt adds whichever of them is missing, along with recommended tools such as git.

On Debian, the Docker service starts after the install and at every boot. Check both, then the versions:

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

The output looks similar to this. Your versions may be newer:

active
enabled
Docker version 29.9.3, build 5457199
Docker Compose version v5.7.0

Step 4: Run a test container

The hello-world image downloads from Docker Hub, prints a message and exits:

sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
...

Step 5: Run Docker as your own user

Without sudo, docker commands fail, because only root and the docker group can open the Docker socket:

docker ps
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

The package creates the docker group with no members. Add your user:

sudo usermod -aG docker $USER

Docker's post-installation page warns that the group grants root-level privileges. A member can start a container that mounts the server's / directory and changes its files, as Docker's security page explains. Add only users you would also give sudo. Log out of SSH and back in so the group applies, then check:

id -nG
docker run --rm hello-world

The group list must include docker, and the test prints Hello from Docker!. For a daemon that runs without root, Docker documents a separate rootless mode.

Step 6: Limit container log size

Docker's default json-file logging driver keeps each container's output with no size limit. Create /etc/docker/daemon.json with a limit of 3 files of 10 MB per container:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF

The values are quoted strings, as Docker's json-file driver page requires. Validate the file, then restart Docker:

sudo dockerd --validate --config-file /etc/docker/daemon.json
sudo systemctl restart docker
configuration OK

The limit applies to containers created after the restart.

Step 7: Keep container ports off the internet

A port published as -p 8080:80 listens on every IPv4 and IPv6 address of the server, and Debian adds no firewall rules of its own: UFW is an optional package, and Debian ships the nftables service disabled. Check your server:

dpkg -s ufw
systemctl is-enabled nftables

Without UFW, the output looks similar to this:

dpkg-query: package 'ufw' is not installed and no information is available
Use dpkg --info (= dpkg-deb --info) to examine archive files.
disabled

Make loopback the default bind address

Replace /etc/docker/daemon.json with this version, which keeps the log limits and binds published ports to 127.0.0.1 unless a port mapping names another address:

sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "ip": "127.0.0.1",
  "default-network-opts": {
    "bridge": {
      "com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"
    }
  }
}
EOF
sudo dockerd --validate --config-file /etc/docker/daemon.json
sudo systemctl restart docker
  • ip sets the default for the default bridge network, which docker run uses, as the dockerd reference describes. default-network-opts sets it for new networks, including the ones Compose creates, as Docker's port publishing page shows.
  • A Compose project started before this change keeps publishing on every address until you run docker compose down and docker compose up -d in its directory.
  • A container that must answer on the internet, such as a reverse proxy, needs an explicit address, for example "0.0.0.0:443:443". That mapping publishes on IPv4 only.

Limit a public port to your IP address

Traffic to a port published on 0.0.0.0 passes Docker's own rules, so filter it in the DOCKER-USER iptables chain, which runs before them. Find your network interface: it is the name after dev, such as eth0, in the output of this command:

ip route show default

The rule below drops IPv4 connections to host port 8081 from every address except YOUR_IP. Rules added with iptables are gone after a reboot, so a systemd unit adds the rule at each boot, after Docker starts. Before you paste the block, replace eth0, 8081 and YOUR_IP in the ExecStart and ExecStop lines with your interface, host port and IP address, the same values in both lines:

sudo tee /etc/systemd/system/docker-user-rules.service > /dev/null <<'EOF'
[Unit]
Description=Limit published Docker ports to one address
Wants=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8081 ! -s YOUR_IP -j DROP
ExecStop=-/usr/sbin/iptables -D DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8081 ! -s YOUR_IP -j DROP

[Install]
WantedBy=multi-user.target
EOF

The rule filters IPv4 only. Publish the port with an explicit address, such as -p 0.0.0.0:8081:80 or "0.0.0.0:8081:80" in Compose, so it does not also listen on the server's IPv6 addresses. With Wants= rather than Requires=, a Docker restart does not add the rule twice, and ExecStop removes the rule when the unit stops. Enable the unit, then list the chain:

sudo systemctl daemon-reload
sudo systemctl enable --now docker-user-rules.service
sudo iptables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER ! -s 198.51.100.20/32 -i eth0 -p tcp -m conntrack --ctorigdstport 8081 -j DROP

--ctorigdstport matches the port the client connected to, before Docker translates it, as Docker's iptables page explains. DOCKER-USER rules stay in place when Docker restarts.

Leave Debian's nftables service off

Debian runs iptables through iptables-nft, so Docker's rules live in nftables tables that sudo nft list ruleset marks "managed by iptables-nft, do not touch!". Docker's Debian install page, read on November 6, 2026, states: "Firewall rules created with nft are not supported on a system with Docker installed."

Debian's nftables service loads /etc/nftables.conf, whose first command is flush ruleset, and runs nft flush ruleset when it stops. Starting, reloading or stopping it deletes every Docker rule, DOCKER-USER included, so a port you limited answers every address again and new containers with published ports fail to start. While the service is active, an upgrade of the nftables package restarts it and deletes the rules again. If the check at the start of this step printed enabled, /etc/nftables.conf can hold rules this server relies on, so recreate them with UFW first. To recover, turn the service off and restart Docker, then the rules unit if you created it:

sudo systemctl disable --now nftables
sudo systemctl restart docker
sudo systemctl restart docker-user-rules

To filter the server's own ports, such as SSH, install UFW with sudo apt install ufw and allow SSH before you enable it. UFW leaves Docker's rules in place and does not filter ports Docker publishes. How to Set Up a UFW Firewall on Ubuntu 26.04 covers the UFW commands.

Step 8: Start a Compose app and check its ports

Compose names the project, network and containers after the directory. Create a project that serves one page:

mkdir -p ~/hello-compose/html
cd ~/hello-compose
echo '<h1>Hello from Docker on Debian</h1>' > html/index.html

Create compose.yaml in the same directory:

tee compose.yaml > /dev/null <<'EOF'
services:
  web:
    image: nginx:1.30.6-alpine
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    restart: unless-stopped
EOF

127.0.0.1 in the port line keeps the app on loopback even without the Step 7 default, and restart: unless-stopped starts it again after a reboot. Start the app:

docker compose up -d

Check that the service runs, serves the page on loopback and uses the log limits:

docker compose ps --services --status running
curl -s http://127.0.0.1:8080
docker port hello-compose-web-1
docker inspect --format '{{json .HostConfig.LogConfig}}' hello-compose-web-1
web
<h1>Hello from Docker on Debian</h1>
80/tcp -> 127.0.0.1:8080
{"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}

From your own computer, run curl --max-time 5 http://YOUR_SERVER_IP:8080 with the server's IP address, such as 203.0.113.10. curl fails with Failed to connect, or with Connection timed out when a firewall drops the packet; a page means the port is public. Remove the test app with docker compose down.

Update Docker

Docker's repository is an apt source, so a system upgrade also updates Docker Engine, containerd and the plugins:

sudo apt update
sudo apt upgrade

To stay on one release, list the versions, install one, and hold both packages:

apt list --all-versions docker-ce
sudo apt install docker-ce=YOUR_VERSION docker-ce-cli=YOUR_VERSION
sudo apt-mark hold docker-ce docker-ce-cli

Replace YOUR_VERSION with a full version string from the list, such as 5:29.9.3-1~debian.13~trixie. Run sudo apt-mark unhold docker-ce docker-ce-cli to upgrade again.

Debian's trixie release notes say to switch extra apt sources at the same time as Debian's own. In a Debian 12 to 13 upgrade, change the suite in docker.sources when you change Debian's sources:

sudo sed -i 's/^Suites: bookworm$/Suites: trixie/' /etc/apt/sources.list.d/docker.sources

Uninstall Docker

If you created the Step 7 unit, stop and disable it first, which also removes its rule:

sudo systemctl disable --now docker-user-rules.service

Remove the packages:

sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

apt then lists the packages that were installed automatically and are no longer required. Check the list, then remove them with sudo apt autoremove. Images, containers, volumes and daemon.json stay on disk. The first command below erases every image, container and volume; the others remove the settings, if you created them, and the repository files:

sudo rm -rf /var/lib/docker /var/lib/containerd
sudo rm -f /etc/docker/daemon.json /etc/systemd/system/docker-user-rules.service
sudo rm /etc/apt/sources.list.d/docker.sources /etc/apt/keyrings/docker.asc
sudo systemctl daemon-reload

Troubleshoot common Docker errors on Debian

SymptomCauseFix
permission denied while trying to connect to the docker API at unix:///var/run/docker.sockYour user is not in the docker group, or the SSH session started before you joined itRun Step 5, then log out and back in
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?Docker is stopped or failed to startsudo systemctl start docker, then sudo journalctl -u docker -n 50. Run the Step 6 validate command
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is runningDocker is disabled, so the server booted without the Docker socketsudo systemctl enable --now docker.socket docker.service
Job for docker.service failed because start of the service was attempted too often.More than 3 Docker starts within 1 minutesudo systemctl reset-failed docker, then sudo systemctl start docker
Package 'docker-ce' has no installation candidate or does not have a Release file from aptdocker.sources is missing, or names linux/ubuntu or a codename Docker does not publish, as on Debian testing or Kali LinuxRun Step 2 again. On Debian testing or a derivative, set Suites to the matching Debian codename, such as trixie
Conflicting values set for option Signed-By from apt updateAn older Docker source file names a different key filegrep -rl download.docker.com /etc/apt/ lists the files that name Docker's repository. Delete each one except docker.sources, then run sudo apt update
Missing key 7EA0A9C3F273FCD8, which is needed to verify signature or Failed to parse keyring (Debian 13), NO_PUBKEY 7EA0A9C3F273FCD8 (Debian 12)docker.sources has no Signed-By line, or its key file is missingRun the Step 2 key commands again and check the Signed-By line in docker.sources
docker-compose: command not foundCompose v1 used docker-compose; Docker's plugin runs as docker composeRun docker compose, with a space
Warning that the attribute version is obsoleteA compose file written for Compose v1Delete the version: line
iptables: No chain/target/match by that name. when a container or network startsDocker's rules were deleted, for example by Debian's nftables serviceRun the Step 7 recovery commands, then re-create any container that failed to start meanwhile, such as with docker compose up -d --force-recreate in its directory
A container port answers from another computerThe port line has no 127.0.0.1, or the project started before the Step 7 defaultAdd 127.0.0.1: to the port line, then docker compose down and docker compose up -d

FAQ

How do I install Docker on Debian 13 Trixie?

Add Docker's apt repository with the trixie suite, then install docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin and docker-compose-plugin, as in Steps 2 and 3. Docker's install docs list Debian 13 as supported, and its repository had Docker Engine 29.9.3 in November 2026.

Should I install docker.io from Debian or Docker's own repository?

Use Docker's repository for current releases. In November 2026, Debian's docker.io was 26.1.5 on Debian 13 and 20.10.24 on Debian 12, against 29.9.3 in Docker's repository. Docker's docs say that before 28.0.0, which covers both Debian builds, hosts on the same network segment can reach ports published on localhost.

Does Debian have a firewall by default?

Debian adds no firewall rules by default. UFW is an optional package, and Debian ships the nftables service disabled, so every listening port answers until you restrict it. Check with dpkg -s ufw and systemctl is-enabled nftables.

Does Docker bypass the firewall on Debian?

Yes. Docker routes published ports in the nat table, before UFW filters traffic, so UFW rules do not apply to them. Docker also does not support rules written with nft. Publish containers on 127.0.0.1, or filter a public port in the DOCKER-USER chain with iptables.

How do I run Docker without sudo on Debian?

Add your user to the docker group with sudo usermod -aG docker $USER, then log out and back in. Only root and docker group members can open /var/run/docker.sock. Members get root-level access to the server, so add only users who already have sudo.

Is Docker Compose included with Docker on Debian?

Yes, when you install docker-compose-plugin from Docker's repository, as in Step 3. Compose then runs as docker compose, with a space, and was version 5.7.0 in November 2026. Debian's own docker-compose package is 2.26.1 on Debian 13 and Compose v1 (1.29.2) on Debian 12.

How do I install Docker on Debian 12 Bookworm?

Use the same steps as for Debian 13. The Step 2 command writes bookworm into docker.sources, and the same packages installed Docker Engine 29.9.3 and Compose 5.7.0 on Debian 12.15. Debian 12 left full support on July 11, 2026 and has Long Term Support until June 30, 2028.

Next steps

Arct Cloud Linux images deploy in under 30 seconds on a Debian VPS. Compare plans.

This work is licensed under CC BY-NC-SA 4.0.