{"id":1063,"date":"2026-11-12T03:00:00","date_gmt":"2026-11-12T03:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1063"},"modified":"2026-11-12T03:00:00","modified_gmt":"2026-11-12T03:00:00","slug":"install-docker-ubuntu-26-04","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/12\/install-docker-ubuntu-26-04\/","title":{"rendered":"How to Install Docker on Ubuntu 26.04 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This guide shows you how to install Docker on Ubuntu 26.04 with Docker Engine, Compose v2, and a verified container in about ten minutes. The final web test listens only on the VPS itself, so you can confirm networking without accidentally publishing a service to 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 Ubuntu Server 26.04 LTS VPS (Resolute), with at least 2 GB of RAM and 10 GB of free disk space for Docker images and containers.<\/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.<\/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 fresh Ubuntu server with root access.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The commands assume a normal sudo-enabled account. If you are logged in as <code>root<\/code>, remove <code>sudo<\/code>; run the user-group command in Step 6 for the account that will administer Docker.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Ubuntu 26.04 and update the VPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release before adding a third-party repository prevents an older Ubuntu codename from being used on a Resolute host.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME|UBUNTU_CODENAME)=' \/etc\/os-release\nuname -m\nsudo apt update\nsudo apt full-upgrade -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The output should identify Ubuntu 26.04 and show a supported 64-bit architecture such as <code>x86_64<\/code> or <code>aarch64<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Ubuntu 26.04 LTS\"\nVERSION_ID=\"26.04\"\nVERSION_CODENAME=resolute\nx86_64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The exact <code>PRETTY_NAME<\/code> text can change with point releases. 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\">Ubuntu&#8217;s <code>docker.io<\/code> package and separately installed runtimes can conflict with the containerd version bundled with Docker&#8217;s official packages.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a new VPS, remove possible conflicting packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">APT may say that some packages are not installed; that is expected. This command does not automatically remove images, containers, volumes, or networks in <code>\/var\/lib\/docker\/<\/code>. On an existing Docker or Podman host, inspect workloads before removing a runtime.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker&#8217;s signed Ubuntu repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker&#8217;s repository provides the Engine, CLI, containerd, Buildx, and Compose plugin as a coordinated package set, while the keyring lets APT verify its metadata.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the keyring prerequisites and create a deb822 source that reads Ubuntu 26.04&#8217;s codename from the host:<\/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\/ubuntu\/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\/ubuntu\nSuites: $(. \/etc\/os-release &amp;&amp; echo \"\\${UBUNTU_CODENAME:-$VERSION_CODENAME}\")\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\">Check that APT can see an Engine candidate from Docker&#8217;s repository before installing it:<\/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>download.docker.com<\/code> and reference the Resolute package stream:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Candidate: 5:29.x.y-1~ubuntu.26.04~resolute\n 500 https:\/\/download.docker.com\/linux\/ubuntu resolute\/stable ...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For the supported release list and repository procedure, see Docker&#8217;s <a href=\"https:\/\/docs.docker.com\/engine\/install\/ubuntu\/\" target=\"_blank\" rel=\"noopener\">Ubuntu installation documentation<\/a>.<\/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\">Installing the daemon and plugins together keeps the Docker client, runtime, image builder, and Compose integration compatible.<\/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\">Confirm that systemd started Docker and that the CLI 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\">Patch versions change as Docker publishes updates, but the shape of the output should be:<\/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&#8217;s installation test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The official <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 of the output, look for the success message:<\/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 container exits after printing the message. The <code>--rm<\/code> flag removes its stopped container record, although the small 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\">The Docker socket is root-owned by default. Membership in the <code>docker<\/code> group removes repeated <code>sudo<\/code> prompts, but it also grants root-equivalent control over the VPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this block while logged in as the normal account that should manage 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\">If you are currently in a root shell, replace <code>\"$USER\"<\/code> with the actual login name in uppercase placeholder form, such as <code>YOUR_LINUX_USER<\/code>; do not add <code>root<\/code> unless that is intentionally the account you use. Log out of SSH completely and reconnect so the new group is loaded.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then verify access without <code>sudo<\/code>:<\/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. <code>newgrp docker<\/code> can load the group in the current shell, but a fresh SSH session avoids stale credentials.<\/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 port publishing while binding only to <code>127.0.0.1<\/code>, so the test does not require a DNS record or an inbound firewall rule.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name ubuntu26-docker-check --publish 127.0.0.1:18083:80 nginx:alpine\ndocker ps --filter name=ubuntu26-docker-check --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'\ncurl --head --max-time 10 http:\/\/127.0.0.1:18083<\/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\nubuntu26-docker-check Up ...        127.0.0.1:18083-&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 ubuntu26-docker-check\ndocker rm ubuntu26-docker-check<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Ubuntu 26.04<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run one final check as the Docker administrator to confirm boot startup, daemon communication, Compose, and local storage:<\/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>, Docker client and 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<aside class=\"vl-element vl-cta-banner\" aria-label=\"Install Docker on Ubuntu 26.04\">\n\t<div class=\"vl-cta-banner__content\">\n\t\t<p class=\"vl-cta-banner__eyebrow\">Ubuntu VPS<\/p>\n\t\t<h3 class=\"vl-cta-banner__title\">Install Docker on Ubuntu 26.04<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get a fresh Ubuntu VPS with root access for Docker, Compose, and container workloads.<\/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&#8217;s repository, or the source file points to a different suite. Check the release, source, and candidate, then run the repository block in Step 3 again if needed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(VERSION_ID|VERSION_CODENAME|UBUNTU_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 source should use <code>resolute<\/code>, and the policy output should show a candidate from <code>download.docker.com<\/code>. Do not replace it with <code>noble<\/code>; that installs packages for Ubuntu 24.04.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>NO_PUBKEY<\/code> 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. Download the key again and ensure it is readable before retrying; do not bypass signature verification or mark the repository as trusted:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo curl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/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<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\">The SSH session started before the account joined the <code>docker<\/code> group, or <code>usermod<\/code> targeted a different account. Reconnect, run <code>id -nG<\/code>, and retry; <code>sudo docker info<\/code> is only a temporary diagnostic while you correct the membership.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>curl: (7) Failed to connect to 127.0.0.1 port 18083<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The image may still be downloading, the container may have exited, or another process may already own port 18083. Inspect the container and listener:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker ps -a --filter name=ubuntu26-docker-check\ndocker logs ubuntu26-docker-check\nsudo ss -ltnp 'sport = :18083'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the port is occupied, remove the failed test and repeat it with <code>--publish 127.0.0.1:28083:80<\/code>, then request <code>http:\/\/127.0.0.1:28083<\/code>. A loopback-only binding needs no public UFW 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 Docker&#8217;s API on its Unix socket. Never expose an unauthenticated Docker API on TCP port 2375; remote administration requires deliberate TLS configuration and narrow source-IP rules.<\/li>\n\n\n<li>Treat the <code>docker<\/code> group as administrative access. Use rootless Docker if you need a stronger privilege boundary than a shared root-equivalent group.<\/li>\n\n\n<li>Keep internal services on <code>127.0.0.1<\/code> until they sit behind a TLS reverse proxy. For a public web application, allow only the required ports in UFW, preserve your current SSH rule, and review Docker&#8217;s <code>DOCKER-USER<\/code> chain because published container ports can bypass ordinary UFW expectations. The <a href=\"https:\/\/vpslake.com\/ufw-firewall-generator\">VPSLake UFW firewall generator<\/a> can help draft a starting ruleset.<\/li>\n\n\n<li>Ubuntu 26.04 no longer supports legacy cgroup v1. Keep the host on its current supported kernel and do not try to run a cgroup-v1-only workload on this release.<\/li>\n\n\n<li>Monitor <code>\/var\/lib\/docker<\/code> and container logs. Image layers and the default JSON log driver can consume disk space even when few containers are running.<\/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 officially supported on Ubuntu 26.04?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes. Docker&#8217;s current Ubuntu instructions list Ubuntu Resolute 26.04 LTS as a supported release and provide packages through Docker&#8217;s signed APT repository. The repository command in this guide reads the host codename so the source stays aligned with the installed release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why use Docker&#8217;s repository instead of Ubuntu&#8217;s <code>docker.io<\/code> package?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu&#8217;s package can be useful when you want distribution-managed versions, but it is not the package path used by Docker&#8217;s official Engine instructions. Docker&#8217;s repository supplies the Engine, containerd, Buildx, and Compose plugin together, which makes the <code>docker compose<\/code> workflow predictable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does Docker work with UFW on Ubuntu 26.04?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">UFW can still protect host services, but Docker-published ports may be inserted into packet-filtering paths that do not behave like ordinary UFW rules. Keep tests on loopback, publish only required application ports, and use Docker&#8217;s <a href=\"https:\/\/docs.docker.com\/engine\/network\/packet-filtering-firewalls\/\" target=\"_blank\" rel=\"noopener\">packet-filtering and firewall guidance<\/a> when you need source-IP restrictions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why is the command <code>docker compose<\/code> instead of <code>docker-compose<\/code>?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Compose plugin is the maintained v2 integration and is installed by <code>docker-compose-plugin<\/code>. It is invoked as a Docker subcommand, so use <code>docker compose up -d<\/code> in a project directory rather than relying on the older standalone hyphenated command.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Is Docker officially supported on Ubuntu 26.04?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes. Docker's current Ubuntu instructions list Ubuntu Resolute 26.04 LTS as a supported release and provide packages through Docker's signed APT repository. The repository command in this guide reads the host codename so the source stays aligned with the installed release.\"}}, {\"@type\": \"Question\", \"name\": \"Why use Docker's repository instead of Ubuntu's `docker.io` package?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Ubuntu's package can be useful when you want distribution-managed versions, but it is not the package path used by Docker's official Engine instructions. Docker's repository supplies the Engine, containerd, Buildx, and Compose plugin together, which makes the `docker compose` workflow predictable.\"}}, {\"@type\": \"Question\", \"name\": \"Does Docker work with UFW on Ubuntu 26.04?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"UFW can still protect host services, but Docker-published ports may be inserted into packet-filtering paths that do not behave like ordinary UFW rules. Keep tests on loopback, publish only required application ports, and use Docker's [packet-filtering and firewall guidance](https:\/\/docs.docker.com\/engine\/network\/packet-filtering-firewalls\/) when you need source-IP restrictions.\"}}, {\"@type\": \"Question\", \"name\": \"Why is the command `docker compose` instead of `docker-compose`?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"The Compose plugin is the maintained v2 integration and is installed by `docker-compose-plugin`. It is invoked as a Docker subcommand, so use `docker compose up -d` in a project directory rather than relying on the older standalone hyphenated command.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Ubuntu 26.04 with Docker&#8217;s official APT repository, Compose v2, non-root access, safe tests, and troubleshooting for fresh VPS deployments.","protected":false},"author":1,"featured_media":1064,"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,118,120,104],"class_list":["post-1063","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-docker","tag-docker-compose","tag-ubuntu-26-04","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1063","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=1063"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1063\/revisions"}],"predecessor-version":[{"id":3996,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1063\/revisions\/3996"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1064"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1063"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1063"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1063"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}