{"id":1049,"date":"2026-11-10T09:00:00","date_gmt":"2026-11-10T09:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1049"},"modified":"2026-11-10T09:00:00","modified_gmt":"2026-11-10T09:00:00","slug":"install-docker-debian-13","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/10\/install-docker-debian-13\/","title":{"rendered":"How to Install Docker on Debian 13 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker on Debian 13 with Docker Engine and Compose v2 from Docker\u2019s signed APT repository. In about ten minutes, you can run a verified container and confirm that Docker starts with the server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Debian 13 is Trixie, so the repository must use the trixie suite. The local web test below binds to loopback only, keeping an unfinished service off the public network.<\/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 13 (Trixie) VPS with at least 2 GB of RAM and 10 GB of free disk space.<\/li>\n\n\n<li>SSH access as root, or a normal Linux account with working sudo privileges.<\/li>\n\n\n<li>Outbound HTTPS access to download.docker.com and Docker Hub for package and image downloads.<\/li>\n\n\n<li>No domain is needed for installation or the local test. You will need a domain later if you put a public application behind HTTPS.<\/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 assume a sudo-enabled account. If you are logged in as root, remove sudo. Run the non-root setup as the account that should administer Docker.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Debian 13 and update the host<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release and architecture first prevents you from adding a repository for the wrong Debian suite.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash grep -E &#8216;^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME)=&#8217; \/etc\/os-release dpkg &#8211;print-architecture sudo apt update sudo apt full-upgrade -y ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The release should say Trixie and a typical VPS should report amd64:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text PRETTY_NAME=&#8221;Debian GNU\/Linux 13 (trixie)&#8221; VERSION_ID=&#8221;13&#8243; VERSION_CODENAME=trixie amd64 ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Minor release details and upgrade counts vary. Fix any mirror, DNS, or interrupted-package error before installing Docker.<\/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 docker.io package and separately installed runtimes can conflict with the versions bundled by Docker Engine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a fresh VPS, remove possible conflicting packages:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo apt remove -y docker.io docker-compose docker-doc docker-buildx podman-docker containerd runc ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">APT may report that some packages are not installed. Removing packages does not automatically erase images, containers, volumes, or networks under \/var\/lib\/docker\/, so inspect that directory before changing an existing Docker host.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker\u2019s signed Trixie repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker\u2019s repository supplies Engine, the CLI, containerd, Buildx, and the maintained Compose v2 plugin as one package set.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the certificate and download tools, save Docker\u2019s key in APT\u2019s keyring directory, and create a deb822 source file for Trixie:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo apt install -y 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 &gt; \/dev\/null &lt;&lt;EOF Types: deb URIs: https:\/\/download.docker.com\/linux\/debian Suites: trixie Components: stable Architectures: $(dpkg &#8211;print-architecture) Signed-By: \/etc\/apt\/keyrings\/docker.asc EOF sudo apt update ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm that APT sees an Engine candidate from Docker\u2019s repository:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash apt-cache policy docker-ce ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The candidate should come from download.docker.com and use a Debian 13\/Trixie package build. Docker\u2019s <a href=\"https:\/\/docs.docker.com\/engine\/install\/debian\/\" target=\"_blank\" rel=\"noopener\">Debian installation documentation<\/a> lists Trixie as supported and documents this repository method.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Install Engine, Buildx, and Compose v2<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These packages install the daemon, command-line client, runtime, image builder, and docker compose integration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable &#8211;now docker ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check the service and both Docker command-line components:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo systemctl is-active docker sudo systemctl is-enabled docker sudo docker version &#8211;format &#8216;Client {{.Client.Version}} \/ Server {{.Server.Version}}&#8217; sudo docker compose version ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output has an active, enabled service and version strings. Docker\u2019s patch versions change as updates are published:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text active enabled Client 29.x.y \/ Server 29.x.y Docker Compose version v2.x.y ~~~<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Run the official smoke test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The hello-world image checks the complete path from the client to the daemon, registry, image cache, container creation, and process startup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo docker run &#8211;rm hello-world ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Near the end of the output, look for this confirmation:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text Hello from Docker! Your installation appears to be working correctly. ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The container exits after printing its message. The &#8211;rm option removes its stopped container record, although the downloaded image remains cached.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Give one account non-root Docker access<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker\u2019s Unix socket is root-owned by default. The docker group avoids repeated sudo, but membership grants root-equivalent control over the VPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the normal account that should manage containers:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo groupadd &#8211;force docker sudo usermod -aG docker &#8220;$USER&#8221; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Log out of SSH completely and reconnect so the new group membership is loaded. Then test access without sudo:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash id -nG docker info &#8211;format &#8216;Server {{.ServerVersion}}&#8217; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The group list should include docker, and the second command should print the Engine version. Use newgrp docker for the current shell if reconnecting is not convenient.<\/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 disposable NGINX container confirms that Docker can publish a port without making the test service reachable from the internet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker run -d &#8211;name debian13-docker-check &#8211;publish 127.0.0.1:18081:80 nginx:alpine docker ps &#8211;filter name=debian13-docker-check &#8211;format &#8216;table {{.Names}}\\t{{.Status}}\\t{{.Ports}}&#8217; curl &#8211;head &#8211;max-time 10 http:\/\/127.0.0.1:18081 ~~~<\/p>\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<p class=\"wp-block-paragraph\">~~~text NAMES                 STATUS        PORTS debian13-docker-check Up &#8230;        127.0.0.1:18081-&gt;80\/tcp HTTP\/1.1 200 OK ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because the host binding is loopback-only, you do not need to open port 18081 in UFW. Remove the test after checking it:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker stop debian13-docker-check docker rm debian13-docker-check ~~~<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Debian 13<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run this final check to confirm boot startup, daemon communication, Compose, and local container storage:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash systemctl is-active docker systemctl is-enabled docker docker version &#8211;format &#8216;Client {{.Client.Version}} \/ Server {{.Server.Version}}&#8217; docker compose version docker info &#8211;format &#8216;Containers {{.Containers}} | Images {{.Images}} | Storage {{.Driver}}&#8217; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Successful output includes active, enabled, Docker client\/server versions, a Compose v2 version, and commonly overlay2 as the storage driver:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text active enabled Client 29.x.y \/ Server 29.x.y Docker Compose version v2.x.y Containers 0 | Images 2 | Storage overlay2 ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For published ports and the way Docker interacts with host filtering, read Docker\u2019s <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 13\">\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 13<\/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\">E: Package docker-ce has no installation candidate<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">APT has not loaded Docker\u2019s repository, or the source file names a suite other than Trixie. Check the release and source, then refresh the package lists:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash grep -E &#8216;^(VERSION_ID|VERSION_CODENAME)=&#8217; \/etc\/os-release grep -E &#8216;^(Types|URIs|Suites|Components|Architectures|Signed-By):&#8217; \/etc\/apt\/sources.list.d\/docker.sources sudo apt update apt-cache policy docker-ce ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The source must show Suites: trixie, and the policy output should show a candidate from download.docker.com. Recreate Step 3 if the file is absent or points to another release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">NO_PUBKEY or an APT signature error<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">APT cannot read the key associated with the Docker source. Check the key\u2019s permissions and download it again before retrying the update:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash 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 apt update ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not bypass signature checks or mark the repository as trusted; the signature verifies that the package metadata came from the expected source.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Permission denied while trying to connect to the Docker daemon socket<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The SSH session started before the account joined the docker group, or the group command targeted a different user. Reconnect and run id -nG, or use newgrp docker; sudo docker info is a temporary workaround.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">curl: (7) Failed to connect to 127.0.0.1 port 18081<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The test image may still be downloading, the container may have stopped, or another process may already own the port. Inspect the container and listener:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker ps -a &#8211;filter name=debian13-docker-check docker logs debian13-docker-check sudo ss -ltnp &#8216;sport = :18081&#8217; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the port is occupied, remove the failed test and repeat it with &#8211;publish 127.0.0.1:28081:80, then curl http:\/\/127.0.0.1:28081. A loopback binding needs no public firewall rule.<\/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 the Docker API on its Unix socket. Never expose an unauthenticated TCP API on port 2375; remote administration requires deliberate TLS configuration and narrow source-IP rules.<\/li>\n\n\n<li>Treat membership in the docker group as administrative access. Use rootless Docker if you need a stronger privilege boundary.<\/li>\n\n\n<li>Publish internal services on 127.0.0.1 and put a TLS reverse proxy in front of them. If an application must be public, open only its required ports and review Docker\u2019s DOCKER-USER chain behavior; published container ports can bypass ordinary UFW filtering. A <a href=\"https:\/\/vpslake.com\/ufw-firewall-generator\">VPSLake UFW firewall generator<\/a> can help draft a starting ruleset.<\/li>\n\n\n<li>Watch \/var\/lib\/docker and container logs. Image layers and default JSON logs can consume disk space even when the host has few running containers.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Does Docker support Debian 13?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes. Docker\u2019s current Debian instructions list Debian Trixie 13 as a supported release and provide packages through the official Debian APT repository. Use the trixie suite shown in this guide rather than substituting bookworm.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need Docker Desktop on a Debian VPS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. Docker Engine is the server-side runtime and is the appropriate choice for a headless VPS. Docker Desktop is a separate desktop application intended for local development environments.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why not install Debian\u2019s docker.io package?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Debian\u2019s package can be useful when you want distribution-managed versions, but it may not match Docker\u2019s current Engine, Buildx, and Compose release cadence. Docker\u2019s signed repository keeps those components aligned and is the method used here.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I expose a container to the public internet safely?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Publish only the ports the application needs, allow those ports in your firewall, and use a domain with TLS for a web service. Review Docker\u2019s packet-filtering behavior first because a published port can be reachable outside the normal UFW rule path.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Does Docker support Debian 13?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes. Docker\u2019s current Debian instructions list Debian Trixie 13 as a supported release and provide packages through the official Debian APT repository. Use the trixie suite shown in this guide rather than substituting bookworm.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need Docker Desktop on a Debian VPS?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. Docker Engine is the server-side runtime and is the appropriate choice for a headless VPS. Docker Desktop is a separate desktop application intended for local development environments.\"}}, {\"@type\": \"Question\", \"name\": \"Why not install Debian\u2019s docker.io package?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Debian\u2019s package can be useful when you want distribution-managed versions, but it may not match Docker\u2019s current Engine, Buildx, and Compose release cadence. Docker\u2019s signed repository keeps those components aligned and is the method used here.\"}}, {\"@type\": \"Question\", \"name\": \"How do I expose a container to the public internet safely?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Publish only the ports the application needs, allow those ports in your firewall, and use a domain with TLS for a web service. Review Docker\u2019s packet-filtering behavior first because a published port can be reachable outside the normal UFW rule path.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Debian 13 with Docker&#8217;s signed APT repository, Compose v2, non-root access, a safe container test, and VPS fixes on a fresh VPS.","protected":false},"author":1,"featured_media":1050,"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,98,118,120],"class_list":["post-1049","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-debian-13","tag-docker","tag-docker-compose","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1049","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=1049"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1049\/revisions"}],"predecessor-version":[{"id":3969,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1049\/revisions\/3969"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1050"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1049"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1049"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1049"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}