{"id":1047,"date":"2026-11-10T03:00:00","date_gmt":"2026-11-10T03:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1047"},"modified":"2026-11-10T03:00:00","modified_gmt":"2026-11-10T03:00:00","slug":"install-docker-debian-12-2","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/10\/install-docker-debian-12-2\/","title":{"rendered":"How to Install Docker on Debian 12 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Debian 12 is Bookworm, so this guide uses Docker\u2019s official Bookworm-compatible APT repository instead of Debian\u2019s older <code>docker.io<\/code> package. The test binds only to localhost, which lets you validate networking without placing an unfinished service on the internet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>A 64-bit Debian 12 (Bookworm) VPS with at least 2 GB of RAM and 10 GB of free disk space.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a normal Linux account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>Outbound HTTPS access to <code>download.docker.com<\/code> and Docker Hub so APT can download packages and Docker can pull images.<\/li>\n\n\n<li>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.<\/li>\n\n\n<li>A <a href=\"https:\/\/vpslake.com\/buy-vps\">VPSLake Linux VPS<\/a> if you need a Debian server with root access for containers.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The commands below assume a sudo-enabled account. If you are logged in as <code>root<\/code>, remove <code>sudo<\/code>; for the non-root setup, replace <code>$USER<\/code> with the existing account that should manage Docker.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Debian 12 and refresh APT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release first prevents you from adding a repository for another Debian codename or architecture.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME)=' \/etc\/os-release\ndpkg --print-architecture\nsudo apt update\nsudo apt full-upgrade -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The release should identify Bookworm and the architecture should normally be <code>amd64<\/code> on a VPS:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Debian GNU\/Linux 12 (bookworm)\"\nVERSION_ID=\"12\"\nVERSION_CODENAME=bookworm\namd64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The minor release and package counts will vary. Resolve any DNS, mirror, or interrupted-package error before continuing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Remove packages that conflict with Docker Engine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Debian\u2019s <code>docker.io<\/code> package and separately installed container runtimes can conflict with the versions Docker ships in its repository.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a fresh VPS, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt remove -y docker.io docker-compose docker-doc docker-buildx podman-docker containerd runc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">APT may report that some packages are not installed. This command does not remove images, containers, volumes, or networks already stored under <code>\/var\/lib\/docker\/<\/code>; inspect existing workloads before changing a server that already runs containers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker\u2019s signed Bookworm repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The repository gives you Docker Engine, its CLI, containerd, Buildx, and the maintained Compose v2 plugin as one compatible package set.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the certificate and download tools, store Docker\u2019s key in the dedicated APT keyring directory, then create a deb822 source file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y ca-certificates curl\nsudo install -m 0755 -d \/etc\/apt\/keyrings\nsudo curl -fsSL https:\/\/download.docker.com\/linux\/debian\/gpg -o \/etc\/apt\/keyrings\/docker.asc\nsudo chmod a+r \/etc\/apt\/keyrings\/docker.asc\nsudo tee \/etc\/apt\/sources.list.d\/docker.sources &gt; \/dev\/null &lt;&lt;EOF\nTypes: deb\nURIs: https:\/\/download.docker.com\/linux\/debian\nSuites: bookworm\nComponents: stable\nArchitectures: $(dpkg --print-architecture)\nSigned-By: \/etc\/apt\/keyrings\/docker.asc\nEOF\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm APT can see Docker packages for Bookworm before installing them:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apt-cache policy docker-ce<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The candidate should come from <code>https:\/\/download.docker.com\/linux\/debian<\/code> and include a Debian 12\/Bookworm package build. Docker\u2019s <a href=\"https:\/\/docs.docker.com\/engine\/install\/debian\/\" target=\"_blank\" rel=\"noopener\">Debian installation guide<\/a> documents this repository method and supported architectures.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Install Docker Engine and Compose v2<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This package set installs the daemon, CLI, container runtime, image builder, and the <code>docker compose<\/code> subcommand.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\nsudo systemctl enable --now docker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check that systemd is running Docker and that the command-line tools can reach the daemon:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl is-active docker\nsudo systemctl is-enabled docker\nsudo docker version --format 'Client {{.Client.Version}} \/ Server {{.Server.Version}}'\nsudo docker compose version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output has an active, enabled service and version strings. Exact patch numbers change as Docker publishes releases:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nenabled\nClient 29.x.y \/ Server 29.x.y\nDocker Compose version v2.x.y<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Run Docker\u2019s built-in smoke test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>hello-world<\/code> image checks the complete path from the CLI to the daemon, registry, image cache, container creation, and process startup.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker run --rm hello-world<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Near the end, look for the following confirmation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hello from Docker!\nYour installation appears to be working correctly.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The test container exits on its own, and <code>--rm<\/code> removes its stopped container record. Its small image remains cached until you remove it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Allow one account to use Docker without sudo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker\u2019s Unix socket is root-owned by default. The <code>docker<\/code> group removes repeated <code>sudo<\/code> prompts, but membership is administrative access because a Docker user can effectively control the host.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the account that should operate containers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd --force docker\nsudo usermod -aG docker \"$USER\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sign out of SSH completely and reconnect, or activate the new group in the current shell with <code>newgrp docker<\/code>. Then check the account\u2019s access:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>id -nG\ndocker info --format 'Server {{.ServerVersion}}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The group list should contain <code>docker<\/code>, and the second command should print the Engine version. Keep using <code>sudo docker<\/code> until the new group is loaded if you prefer not to start a child shell.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Test container networking on loopback<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name debian12-docker-check --publish 127.0.0.1:18080:80 nginx:alpine\ndocker ps --filter name=debian12-docker-check --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'\ncurl --head --max-time 10 http:\/\/127.0.0.1:18080<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a running container and an HTTP success response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>NAMES                  STATUS        PORTS\ndebian12-docker-check  Up ...        127.0.0.1:18080-&gt;80\/tcp\nHTTP\/1.1 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the disposable test when the response is confirmed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker stop debian12-docker-check\ndocker rm debian12-docker-check<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Debian 12<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use this final check to confirm startup, daemon access, Compose, and the local storage driver:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl is-active docker\nsystemctl is-enabled docker\ndocker version --format 'Client {{.Client.Version}} \/ Server {{.Server.Version}}'\ndocker compose version\ndocker info --format 'Containers {{.Containers}} | Images {{.Images}} | Storage {{.Driver}}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Successful output includes <code>active<\/code>, <code>enabled<\/code>, client\/server versions, a Compose v2 version, and commonly <code>overlay2<\/code> as the storage driver:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nenabled\nClient 29.x.y \/ Server 29.x.y\nDocker Compose version v2.x.y\nContainers 0 | Images 2 | Storage overlay2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For Docker\u2019s interaction with published ports and host firewall rules, read the official <a href=\"https:\/\/docs.docker.com\/engine\/network\/packet-filtering-firewalls\/\" target=\"_blank\" rel=\"noopener\">packet-filtering and firewall guidance<\/a>.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Run Docker on Debian\">\n\t<div class=\"vl-cta-banner__content\">\n\t\t<p class=\"vl-cta-banner__eyebrow\">Debian VPS<\/p>\n\t\t<h3 class=\"vl-cta-banner__title\">Run Docker on Debian<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get a root-access Linux VPS for Docker apps, Compose projects, and development environments.<\/p>\n\t<\/div>\n\t<a class=\"vl-cta-banner__button\" href=\"\/buy-vps\" aria-label=\"View Linux VPS plans\">View Linux VPS plans<\/a>\n<\/aside>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>E: Package 'docker-ce' has no installation candidate<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">APT has not loaded Docker\u2019s repository, or the source file uses a suite that does not match Debian 12. Check the release and source, then refresh the package lists:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(VERSION_ID|VERSION_CODENAME)=' \/etc\/os-release\ngrep -E '^(Types|URIs|Suites|Components|Architectures|Signed-By):' \/etc\/apt\/sources.list.d\/docker.sources\nsudo apt update\napt-cache policy docker-ce<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The suite must be <code>bookworm<\/code>, and the policy output should show a candidate from <code>download.docker.com<\/code>. Recreate Step 3 if the file is missing or has a different suite.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>NO_PUBKEY<\/code> or a signature verification error<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">APT cannot read the key used by the Docker source. Check that <code>\/etc\/apt\/keyrings\/docker.asc<\/code> exists and is readable, then run the key download and <code>chmod<\/code> commands from Step 3 again before retrying <code>sudo apt update<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>permission denied while trying to connect to the Docker daemon socket<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Your current SSH process was created before the account joined the <code>docker<\/code> group, or the group command targeted another username. Reconnect and run <code>id -nG<\/code>; <code>newgrp docker<\/code> is a temporary current-shell fix. You can also use <code>sudo docker info<\/code> while correcting the account setup.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>WARNING: Error loading config file ... permission denied<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Running Docker with <code>sudo<\/code> can create a root-owned <code>~\/.docker<\/code> directory in your user\u2019s home. If you do not need settings stored there, remove that directory; otherwise repair its ownership and permissions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chown \"$USER\":\"$USER\" \"$HOME\/.docker\" -R\nsudo chmod g+rwx \"$HOME\/.docker\" -R<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>bind: address already in use<\/code> during the NGINX test<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Another local process owns port 18080. Find the listener and choose another loopback port, such as 28080:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo ss -ltnp 'sport = :18080'\ndocker run -d --name debian12-docker-check --publish 127.0.0.1:28080:80 nginx:alpine\ncurl --head --max-time 10 http:\/\/127.0.0.1:28080<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Stop and remove the test container afterward as shown in Step 7.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardening<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Do not publish Docker\u2019s API on TCP port 2375. Keep administration on the Unix socket unless you have deliberately configured TLS and restricted the source IPs.<\/li>\n\n\n<li>Bind internal applications to <code>127.0.0.1<\/code> and place a TLS reverse proxy in front of them. When a service must be public, allow only its required ports and review Docker\u2019s <code>DOCKER-USER<\/code> chain guidance; published container ports can bypass ordinary <code>ufw<\/code> filtering. A <a href=\"https:\/\/vpslake.com\/ufw-firewall-generator\">VPSLake UFW firewall generator<\/a> can help create a starting ruleset.<\/li>\n\n\n<li>Treat the <code>docker<\/code> group as root-equivalent access. For a stronger privilege boundary, evaluate Docker rootless mode before adding users to the group.<\/li>\n\n\n<li>Monitor <code>\/var\/lib\/docker<\/code> and container logs. Image layers and default JSON logs can consume disk even when the host itself looks lightly used.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Is Docker Engine available for Debian 12?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes. Docker\u2019s current Debian instructions list Bookworm 12 as a supported release and provide packages through Docker\u2019s Debian repository. The repository is preferable here because it supplies Docker Engine and the maintained Compose plugin together.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need <code>docker-compose<\/code> on Debian 12?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. The old <code>docker-compose<\/code> package is a legacy standalone command. Install <code>docker-compose-plugin<\/code> and use <code>docker compose<\/code> with a space; this is the current Compose v2 interface.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should I open port 18080 in UFW?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. The example publishes <code>127.0.0.1:18080<\/code>, so it accepts connections only from the VPS itself and needs no public firewall rule. If you later publish an application on <code>0.0.0.0<\/code>, review Docker\u2019s firewall behavior before assuming a normal UFW allow or deny rule protects that container.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where does Docker store its images and containers?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The default Docker data root is <code>\/var\/lib\/docker<\/code>, while containerd keeps runtime data under <code>\/var\/lib\/containerd<\/code>. 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.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Is Docker Engine available for Debian 12?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes. Docker\u2019s current Debian instructions list Bookworm 12 as a supported release and provide packages through Docker\u2019s Debian repository. The repository is preferable here because it supplies Docker Engine and the maintained Compose plugin together.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need `docker-compose` on Debian 12?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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.\"}}, {\"@type\": \"Question\", \"name\": \"Should I open port 18080 in UFW?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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\u2019s firewall behavior before assuming a normal UFW allow or deny rule protects that container.\"}}, {\"@type\": \"Question\", \"name\": \"Where does Docker store its images and containers?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Debian 12 from Docker&#8217;s signed APT repository, enable Compose v2, test a container, and fix common VPS errors quickly on fresh VPS hosts.","protected":false},"author":1,"featured_media":1048,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"csco_singular_sidebar":"","csco_page_header_type":"","csco_page_load_nextpost":"","footnotes":""},"categories":[5,9],"tags":[119,97,118,120],"class_list":["post-1047","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-debian-12","tag-docker","tag-docker-compose","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1047","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/comments?post=1047"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1047\/revisions"}],"predecessor-version":[{"id":3965,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1047\/revisions\/3965"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1048"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1047"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1047"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}