{"id":993,"date":"2026-11-03T09:00:00","date_gmt":"2026-11-03T09:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=993"},"modified":"2026-11-03T09:00:00","modified_gmt":"2026-11-03T09:00:00","slug":"install-docker-debian-11","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/03\/install-docker-debian-11\/","title":{"rendered":"How to Install and Use Docker on Debian 11 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker on Debian 11 with Docker Engine, the Compose v2 plugin, and a working test container in about ten minutes. You will also run a small Nginx container locally and learn the commands to inspect, stop, and remove containers safely.<\/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 GNU\/Linux 11 (Bullseye) VPS. Debian 11 is an older release, so plan an upgrade to a supported Debian version for long-lived production workloads.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a normal account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>At least 2 GB of RAM and 10 GB of free disk space for the engine, images, logs, and your first workloads.<\/li>\n\n\n<li>Outbound HTTPS access so APT can reach Docker&#8217;s package repository and Docker Hub can provide images.<\/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.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Docker&#8217;s package repository still has a Bullseye suite, but Debian 11 itself is no longer the best choice for a new server. Keep the suite name as <code>bullseye<\/code> in the commands below; do not substitute <code>bookworm<\/code> or <code>trixie<\/code> on a Debian 11 host.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Debian 11 and update APT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm the release before adding a repository so packages cannot be mixed accidentally between Debian versions.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \/etc\/os-release\ndpkg --print-architecture\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The release should identify Bullseye, and the architecture should normally be <code>amd64<\/code> or <code>arm64<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Debian GNU\/Linux 11 (bullseye)\"\namd64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If <code>apt update<\/code> reports repository errors, fix those first. Docker&#8217;s repository cannot make a broken Debian mirror or DNS configuration work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Add Docker&#8217;s signed APT repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The keyring and deb822 source file restrict APT to packages signed by Docker&#8217;s repository key; this replaces the obsolete <code>apt-key<\/code> workflow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the two tools needed to download and validate the repository metadata:<\/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<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a source entry that explicitly targets Bullseye:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tee \/etc\/apt\/sources.list.d\/docker.sources &gt;\/dev\/null &lt;&lt;'EOF'\nTypes: deb\nURIs: https:\/\/download.docker.com\/linux\/debian\nSuites: bullseye\nComponents: stable\nArchitectures: amd64\nSigned-By: \/etc\/apt\/keyrings\/docker.asc\nEOF\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>Architectures<\/code> line matches the most common VPS architecture. If <code>dpkg --print-architecture<\/code> returned <code>arm64<\/code>, change that one line to <code>Architectures: arm64<\/code> before running <code>sudo apt update<\/code> again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check that APT sees Docker packages from the expected host:<\/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\">You should see a candidate package and a repository URL containing <code>download.docker.com<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker-ce:\n  Candidate: 29.x.y-1~debian.11~bullseye\n  500 https:\/\/download.docker.com\/linux\/debian bullseye\/stable amd64 Packages<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The exact version changes as Docker publishes updates; do not copy the sample version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Docker&#8217;s <a href=\"https:\/\/docs.docker.com\/engine\/install\/debian\/\" target=\"_blank\" rel=\"noopener\">Debian installation documentation<\/a> explains the repository layout and package choices.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Install Docker Engine and Compose<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Install the daemon, CLI, container runtime, Buildx builder, and Compose v2 plugin together so the commands in this guide use one compatible package set.<\/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\">On Debian, the package normally starts the daemon automatically. The second command makes the desired boot behavior explicit and also starts Docker immediately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check the service and installed clients:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl is-active docker\ndocker --version\ndocker compose version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output is similar to this, with release numbers that may differ:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nDocker version 29.x.y, build ...\nDocker Compose version v2.x.y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If APT reports a conflict with an existing <code>docker.io<\/code>, <code>docker-compose<\/code>, <code>containerd<\/code>, or <code>runc<\/code> installation, stop and review that installation before removing packages. Do not delete <code>\/var\/lib\/docker<\/code> unless you intentionally want to remove existing images, containers, and volumes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Allow a sudo user to run Docker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker&#8217;s Unix socket is root-owned by default. The <code>docker<\/code> group is convenient for administration, but membership grants privileges equivalent to root, so add only trusted accounts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the non-root account that should manage containers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DOCKER_USER=\"$(id -un)\"\nsudo groupadd --force docker\nsudo usermod -aG docker \"$DOCKER_USER\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Log out of SSH completely and reconnect so the new group is loaded. As a temporary alternative, start a new shell with <code>newgrp docker<\/code>. Confirm the account can access the socket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>id -nG\ndocker info --format '{{.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 Docker Engine version. If you stay logged in as <code>root<\/code>, you can skip this step and prefix later Docker commands with <code>sudo<\/code> when needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Run a known-good test image<\/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, image registry, container runtime, and terminal output.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run --rm hello-world<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Docker downloads the image if it is not already cached, creates a short-lived container, prints a confirmation, and removes that container because of <code>--rm<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hello from Docker!\nThis message shows that your installation appears to be working correctly.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The image is not a server and will exit immediately. That is expected; it is an installation test, not a long-running workload.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Run and manage a web container<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A detached Nginx container demonstrates image pulls, port publishing, status checks, logs, and cleanup without exposing a new public firewall port.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name demo-web -p 127.0.0.1:8080:80 nginx:alpine\ndocker ps --filter name=demo-web\ncurl -I http:\/\/127.0.0.1:8080<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>127.0.0.1<\/code> binding means only processes on the VPS can reach port 8080, so no inbound firewall rule is required for this demonstration. The status should show <code>Up<\/code>, and <code>curl<\/code> should receive an HTTP response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONTAINER ID   IMAGE          COMMAND                  STATUS         PORTS\n...            nginx:alpine  \"\/docker-entrypoint\u2026\"   Up ...          127.0.0.1:8080-&gt;80\/tcp\nHTTP\/1.1 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Useful management commands are:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker logs demo-web\ndocker ps -a\ndocker stop demo-web\ndocker rm demo-web<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>docker ps<\/code> lists running containers, while <code>docker ps -a<\/code> also shows stopped ones. Stopping preserves a container for later use; removing it deletes that container&#8217;s writable layer, but does not delete the <code>nginx:alpine<\/code> image.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Debian 11<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run this final check after reconnecting as your normal Docker administrator. It confirms the daemon, Compose plugin, test image, and absence of the demo container.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl is-enabled docker\nsystemctl is-active docker\ndocker version --format 'Client {{.Client.Version}} \/ Server {{.Server.Version}}'\ndocker compose version\ndocker image ls hello-world\ndocker ps -a --filter name=demo-web<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You want <code>enabled<\/code>, <code>active<\/code>, matching client\/server version output, a Compose v2 version, and no <code>demo-web<\/code> row after the cleanup commands. Docker is ready for application images or a Compose project.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Run Docker on Bullseye\">\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 Bullseye<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Deploy containers on a Debian VPS with root access and room for your next workload.<\/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 did not load Docker&#8217;s Bullseye index, or the source file uses the wrong suite or architecture. Check the source and refresh the cache:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \/etc\/apt\/sources.list.d\/docker.sources\ndpkg --print-architecture\nsudo apt update\napt-cache policy docker-ce<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The source must say <code>Suites: bullseye<\/code>, and its architecture must match <code>dpkg --print-architecture<\/code>. Fix any <code>apt update<\/code> error before retrying the install.<\/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 or validate the repository key. Re-download the key and ensure it is world-readable, then update the cache:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo 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 apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the error mentions a system clock, check <code>date -u<\/code> and repair time synchronization before trusting repository metadata.<\/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 session does not include the new <code>docker<\/code> group. Start a fresh login session or run <code>newgrp docker<\/code>, then confirm with <code>id -nG<\/code>. Use <code>sudo docker ...<\/code> temporarily if you have not yet changed sessions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>Cannot connect to the Docker daemon<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The service is stopped or failed during startup. Inspect the unit and recent journal entries, then start it again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status docker --no-pager\nsudo journalctl -u docker -n 50 --no-pager\nsudo systemctl restart docker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If it fails again, look for a conflicting <code>containerd<\/code>, invalid daemon configuration under <code>\/etc\/docker\/<\/code>, or a VPS kernel that does not provide the required container features.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The container works locally but not from the Internet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The example intentionally binds port 8080 to <code>127.0.0.1<\/code>. Binding a container to <code>0.0.0.0<\/code> exposes it on every host interface, and Docker&#8217;s packet-filter rules can bypass ordinary UFW expectations. Before publishing a public port, read <a href=\"https:\/\/docs.docker.com\/engine\/network\/packet-filtering-firewalls\/\" target=\"_blank\" rel=\"noopener\">Docker&#8217;s firewall guidance<\/a>, permit only the needed service, and place an HTTP service behind TLS and an authenticated reverse proxy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardening<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Keep application ports bound to <code>127.0.0.1<\/code> unless they must be public; publish only the host ports you have intentionally filtered.<\/li>\n\n\n<li>Keep the Docker Engine and images updated, and remove unused images with <code>docker image prune<\/code> after reviewing what will be deleted.<\/li>\n\n\n<li>Add log rotation before running noisy production workloads; unbounded container logs can consume the VPS disk.<\/li>\n\n\n<li>Treat every member of the <code>docker<\/code> group as an administrator. For stronger isolation, evaluate Docker rootless mode instead of granting socket access.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Can I install Docker on Debian 11 today?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes, the commands above target Docker&#8217;s Bullseye repository, but Debian 11 is an older operating system release. For a new production VPS, choose a currently supported Debian release and use its matching suite instead of keeping Bullseye indefinitely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Is <code>docker compose<\/code> different from <code>docker-compose<\/code>?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes. <code>docker compose<\/code> is the current Compose v2 CLI plugin installed by the Docker repository packages. The older hyphenated <code>docker-compose<\/code> command is a separate legacy tool and is not needed for this setup.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why does Docker need <code>containerd.io<\/code>?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Docker Engine uses containerd as part of its runtime stack to manage image and container lifecycle operations. Installing the package supplied by Docker keeps that dependency aligned with the Engine packages rather than mixing it with an unrelated distribution version.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I expose a container safely?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Start by binding it to loopback and put a reverse proxy on the host if it needs HTTPS, authentication, or rate limiting. If you publish directly on a public interface, account for Docker&#8217;s firewall chains and allow only the required source addresses and destination port.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Can I install Docker on Debian 11 today?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes, the commands above target Docker's Bullseye repository, but Debian 11 is an older operating system release. For a new production VPS, choose a currently supported Debian release and use its matching suite instead of keeping Bullseye indefinitely.\"}}, {\"@type\": \"Question\", \"name\": \"Is `docker compose` different from `docker-compose`?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes. `docker compose` is the current Compose v2 CLI plugin installed by the Docker repository packages. The older hyphenated `docker-compose` command is a separate legacy tool and is not needed for this setup.\"}}, {\"@type\": \"Question\", \"name\": \"Why does Docker need `containerd.io`?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Docker Engine uses containerd as part of its runtime stack to manage image and container lifecycle operations. Installing the package supplied by Docker keeps that dependency aligned with the Engine packages rather than mixing it with an unrelated distribution version.\"}}, {\"@type\": \"Question\", \"name\": \"How do I expose a container safely?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Start by binding it to loopback and put a reverse proxy on the host if it needs HTTPS, authentication, or rate limiting. If you publish directly on a public interface, account for Docker's firewall chains and allow only the required source addresses and destination port.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Debian 11 with Docker&#8217;s signed APT repository, Compose v2, non-root access, a test container, and fixes for common errors.","protected":false},"author":1,"featured_media":994,"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,96,118,120],"class_list":["post-993","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-debian-11","tag-docker","tag-docker-compose","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/993","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=993"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/993\/revisions"}],"predecessor-version":[{"id":3867,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/993\/revisions\/3867"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/994"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=993"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=993"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=993"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}