Install Docker on Debian 13 and 12 from apt
Install Docker Engine on Debian 13 or 12 from Docker's apt repository, settle docker.io vs docker-ce with apt policy, and avoid the firewall and root traps.
How to install Docker on Debian 13 or 12
To install Docker on Debian, add Docker's own apt repository, then install the docker-ce packages from it. On 5 November 2026, Docker's install page lists two Debian releases: Debian 13 (trixie) and Debian 12 (bookworm). Debian 11 (bullseye) is not on that list.
The install takes about ten commands. Two things after the install matter more: published container ports skip your firewall, and the docker group is equal to root. This guide covers both.
Versions change from week to week, so this guide does not print the output you will get. It tells you what to look for in your own output.
Which Debian versions does Docker support?
Check your release and your CPU architecture first, because the repository setup depends on both.
. /etc/os-release && echo "$PRETTY_NAME ($VERSION_CODENAME)"
dpkg --print-architectureThe first line should name trixie or bookworm. The second line should be one of the architectures Docker lists for Debian: amd64, armhf, arm64 or ppc64el. If you are still deciding which CPU type to rent, read how ARM and x86 VPS plans differ.
Debian 13 is the current stable release. Debian 12 is oldstable, and the Debian wiki lists its LTS (long-term support) period as 2026-06-11 to 2028-06-30. If you are building a new server, pick Debian 13. If you are still comparing systems, choosing a Linux OS for your VPS walks through that decision.
Debian 11 needs an upgrade first. Docker's install page does not list it, and the Debian wiki shows that its LTS period ended on 2026-08-31. You may notice that download.docker.com still has a bullseye directory. It also has directories for wheezy and jessie. A directory on that server does not mean Docker supports the release: the supported list is the one on the install page. Follow the Debian 11 end of life upgrade path, then come back here.
On Debian testing, or on a derivative such as Kali Linux, the codename in the steps below will not match. Docker's page says to replace it with the codename of the matching Debian release, for example trixie.
Do you have sudo and curl on a minimal Debian?
Some minimal Debian images do not include sudo. Check before you start.
command -v sudo curlThe command prints one path for each tool it finds. A missing line means a missing tool. The repository step below installs curl, so only sudo needs attention here. If it is missing, log in as root and add it. Replace YOUR_USER with your normal login name.
apt update
apt install -y sudo
usermod -aG sudo YOUR_USERLog out and log back in as that user, because group membership is read at login. Then run sudo whoami. It should print root. This is also a good moment to harden SSH on your VPS, since you are about to run more services on it.
docker.io or docker-ce: what is the difference?
These are two different builds from two different groups. Debian's package is called docker.io. The Debian Go Packaging Team maintains it and builds it from Debian's own source, and it depends on Debian's own containerd and runc packages. Docker's package is called docker-ce, and it comes from Docker's repository with Docker's own containerd.io.
The visible difference is age. On 5 November 2026, Debian had docker.io 26.1.5 in trixie and 20.10.24 in bookworm, while Docker's latest Engine release was 29.9.3. Those numbers will move, so do not rely on them. The section on apt policy below shows how to read the current values on your own server.
The packages are also split differently. In trixie, docker.io lists docker-cli as a recommended package, and Compose is a separate package named docker-compose. Docker's repository gives you docker-ce, docker-ce-cli, containerd.io and two plugins for Buildx and Compose.
Choose one source and keep it. This guide uses Docker's repository, because Docker's documentation describes those packages. Debian's package is a fair choice if you want everything to come from the Debian archive and its version does what you need.
Remove packages that conflict with docker-ce
Docker's page says to remove conflicting packages before you install. This command covers Debian's Docker packages, the Podman shim, and standalone containerd and runc.
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc docker-buildx podman-docker containerd runc | cut -f1)The dpkg --get-selections part lists only the packages that are installed, so apt is asked to remove those and nothing else. On a fresh server there may be nothing to remove.
This step does not delete your data. Images, containers, volumes and custom configuration in /var/lib/docker stay on the disk. Read the list that apt shows you before you confirm, because containerd and runc may be used by other software on a server that is not fresh.
Add Docker's apt repository in deb822 format
These commands install two helper packages, download Docker's GPG (GNU Privacy Guard) signing key, and write one repository file. The file uses deb822, the multi-line format that apt reads from .sources files.
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
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
sudo apt updateThe shell fills in Suites and Architectures for you, so you do not edit anything by hand on Debian 13 or 12. The tee command prints the finished file. Read it: Suites should show your codename. The Signed-By line means apt accepts packages from this repository only if they are signed by this key.
The last sudo apt update should fetch from download.docker.com with no errors. If this server had Docker before, an old repository file may still exist. Look for one:
grep -rl download.docker.com /etc/apt/The only match should be docker.sources. A second match, such as an old docker.list, means two files declare the same repository. If apt reports a duplicate source or a Signed-By problem, follow the steps to fix duplicate apt sources and deb822 files.
Compare docker.io and docker-ce with apt policy
Your server now knows both packages. This is where you settle the docker.io question with current data.
apt policy docker.io docker-ce
apt show docker.io
apt show docker-ceIn the apt policy output, read two things for each package. The Candidate line is the version apt would install today. The version table under it shows which server offers that version. docker-ce should come from download.docker.com, and docker.io should come from your Debian mirror.
In the apt show output, compare the Maintainer, Depends and Recommends fields. Those fields answer what each package pulls in on the day you read them.
If docker-ce has no candidate, apt did not read the new repository. Check the tee output from the last section, then run sudo apt update again and read its errors.
Install Docker Engine and the Compose plugin
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginThis installs the latest version in the repository. To choose a version, list them, copy one full string from the list, and use it for both Engine packages.
apt list --all-versions docker-ce
VERSION_STRING=paste-the-version-string-here
sudo apt install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin docker-compose-pluginHow do you confirm the Docker install worked?
Run three checks, in this order. Each one proves something the last one did not.
First, check the service.
sudo systemctl status dockerThe service should be active. If it is not, start it with sudo systemctl start docker, then read the log with sudo journalctl -u docker -n 50. Docker's documentation says Docker starts on boot by default on Debian. You can confirm that on your server with systemctl is-enabled docker containerd. If either service is not enabled, run sudo systemctl enable docker.service containerd.service.
Second, run a real container.
sudo docker run hello-worldThis downloads a small test image from Docker Hub and runs it in a container. A confirmation message from the container means the client and the daemon both work, and the server can reach Docker Hub.
Third, check the client, the server and the Compose plugin.
sudo docker version
docker compose versiondocker version should show a Client section and a Server section. A missing Server section means the client cannot reach the daemon. docker compose version should print a version line. Compose has its own version numbers, so do not expect it to match the Engine version.
If the hello-world pull fails with You have reached your pull rate limit, the cause is Docker Hub and not your install. Docker Hub allows 100 pulls per 6 hours for each unauthenticated IPv4 address or IPv6 /64 subnet, and 200 for an authenticated Personal account. On a shared address, other users can use up that limit. Run sudo docker login, or wait for the window to pass.
Why not use the get.docker.com script on a server?
Docker's own page says the convenience script is not recommended for production. It also says the script is not designed to upgrade an existing install, and that running it again can cause problems with repositories that already exist. The manual steps above take a few more minutes, and they leave you with a repository file you can read and packages that apt manages.
How do Docker upgrades work on Debian?
Docker's packages upgrade through apt, like other packages. Docker's instruction is to repeat the install step and choose the new version.
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker psPlan upgrades on a server with live containers. Docker's documentation says that by default, when the daemon terminates, it shuts down running containers. So compare sudo docker ps before and after the upgrade, and set a restart policy on anything that must come back. For Compose stacks, read how to start Compose services automatically on boot.
Docker's live restore option keeps containers running while the daemon is down. Set it in /etc/docker/daemon.json. This command overwrites that file, so if you already have one, add the key by hand.
sudo tee /etc/docker/daemon.json <<'EOF'
{
"live-restore": true
}
EOF
sudo systemctl reload docker
sudo docker infoFind the live restore line in the docker info output to confirm the setting. Live restore has one real limit: Docker supports it for patch releases only, and not for major daemon upgrades.
Why do published Docker ports bypass ufw and firewalld?
Read this section before you publish any port. Docker's install page says it directly: if you use ufw (uncomplicated firewall) or firewalld, ports you expose with Docker bypass your firewall rules. Docker's firewall page says Docker and ufw are incompatible, because packets are routed before the firewall rules can be applied.
The cause is the default publish address. With -p 8080:80, the daemon publishes the port on all host addresses, both 0.0.0.0 and [::]. Docker's own words are that publishing container ports is insecure by default. A ufw rule that denies 8080 does not stop that traffic.
The everyday fix is to publish on loopback, the address that only the server itself can reach. This test uses Docker's own example.
sudo docker run -d --name web -p 127.0.0.1:8080:80 -p '[::1]:8080:80' nginx
curl -I http://127.0.0.1:8080The curl command should show response headers from nginx. Now test from a different machine, with your server's public address in place of SERVER_IP:
curl -I --max-time 5 http://SERVER_IP:8080That request should fail, which means the container is reachable only from the server. Remove the test container with sudo docker rm -f web.
In a Compose file, put the address in the ports entry. Leave the address out and Compose binds to all interfaces.
services:
web:
image: nginx
ports:
- "127.0.0.1:8080:80"Some ports must stay public but limited, for example to one office address. For those, use the DOCKER-USER chain. Docker processes that chain before its own rules, and it is the place Docker gives for filtering published ports. The commands are in the guide to why Docker bypasses UFW and how to fix it.
Two more rules from Docker's documentation apply on Debian. Docker creates its firewall rules with iptables by default, so add custom rules with iptables or ip6tables. Docker Engine 29 also has an experimental nftables backend, selected with the firewall-backend daemon option. That backend has no DOCKER-USER chain: your own nft rules go in separate tables, and you should not modify Docker's tables directly. Also do not set "iptables": false in the daemon configuration. Docker warns that this is likely to break container networking.
Run Docker without root: the docker group and rootless mode
The Docker daemon runs as root and owns a Unix socket. Your user cannot reach that socket without sudo, unless you give it access. There are two common choices.
The docker group
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
docker run hello-worldIf the group already exists, groupadd reports that and you can continue. The newgrp command applies the new group in this shell only. Log out and log back in to apply it everywhere. The last command should now work with no sudo.
If you ran Docker with sudo earlier, you may see this:
WARNING: Error loading config file: /home/user/.docker/config.json - stat /home/user/.docker/config.json: permission deniedThe cause is that sudo left ~/.docker with the wrong owner or permissions. Fix it:
sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
sudo chmod g+rwx "$HOME/.docker" -RNow the cost. Docker's documentation says the docker group grants root-level privileges. The reason is that Docker can mount the host's / directory into a container, and that container can then change the host filesystem without any restriction. So a stolen account in the docker group is a stolen root account. Add only people you already trust with root. On a server, sudo docker for each command is a reasonable choice.
Rootless mode
Rootless mode runs the daemon and the containers as a normal user, so a user does not get root through Docker. Your user needs at least 65,536 subordinate user IDs and group IDs. Check them:
grep ^$(whoami): /etc/subuid
grep ^$(whoami): /etc/subgidEach command should print one line with a range of that size. Then install the helpers, stop the system daemon, and run the setup tool as your normal user. Do not use sudo on the last command.
sudo apt install uidmap docker-ce-rootless-extras
sudo systemctl disable --now docker.service docker.socket
sudo rm /var/run/docker.sock
dockerd-rootless-setuptool.sh installThe tool prints lines to add to your shell profile, so copy them. It also creates a rootless CLI (command-line interface) context and makes it the current one. Run docker info: the client section should show the rootless context, and Security Options should list rootless.
Manage the daemon with systemctl --user start docker.service. To start it at boot, run sudo loginctl enable-linger $USER. Docker's rootless mode page has the full reference. Read it before you move real workloads.
How do you uninstall Docker from Debian?
The purge command removes the packages. It does not remove your images, containers or volumes, so the next two rm lines delete them. Those two lines cannot be undone, so back up your volumes first.
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
sudo rm /etc/apt/sources.list.d/docker.sources
sudo rm /etc/apt/keyrings/docker.ascAny configuration file you edited, such as /etc/docker/daemon.json, stays until you delete it by hand. For what purge keeps and removes compared with remove, read apt remove vs purge vs autoremove.
What to do after the install
Your server now runs Docker from a repository you can inspect. Before you start real services, set limits so one container cannot use all the RAM: memory limits in Docker Compose covers this. If you want ideas for what to run first, read what to self-host in 2026.
FAQ
Can I install Docker on Debian 11?
Docker's install page lists only Debian 13 (trixie) and Debian 12 (bookworm) as of 5 November 2026. Debian 11 is not listed, and the Debian wiki shows its LTS period ended on 2026-08-31. A bullseye directory still exists on Docker's download server, but that is not a statement of support. Upgrade the server to a listed Debian release first, then install Docker.
What is the difference between docker.io and docker-ce on Debian?
docker.io is Debian's build, maintained by the Debian Go Packaging Team, and it depends on Debian's containerd and runc. docker-ce is Docker's build from Docker's own apt repository. On 5 November 2026, Debian's version was older in both trixie and bookworm. Run apt policy docker.io docker-ce to see both versions and their sources on your server. Install one and remove the other, because Docker's page treats docker.io as a conflicting package.
How do I check that Docker is installed correctly on Debian?
Run sudo systemctl status docker and confirm the service is active. Then run sudo docker run hello-world, which downloads a test image and prints a confirmation message from a container. Finish with sudo docker version to see both the Client and Server sections, and docker compose version to confirm the Compose plugin.
Why does ufw not block a Docker container port on Debian?
Docker's traffic for published ports is routed before ufw's rules can apply, and a port published without an address binds to all host addresses. Publish internal services on loopback, such as -p 127.0.0.1:8080:80, and test from another machine. For a port that must stay public, filter it in the DOCKER-USER chain with iptables or ip6tables.
Is it safe to add my user to the docker group?
Only if you trust that user with root. Docker's documentation says the docker group grants root-level privileges, because a member can mount the host's / directory into a container and change any file. On a shared server, keep using sudo, or use rootless mode so the daemon and containers run as a normal user.