{"id":1057,"date":"2026-11-11T09:00:00","date_gmt":"2026-11-11T09:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1057"},"modified":"2026-11-11T09:00:00","modified_gmt":"2026-11-11T09:00:00","slug":"install-docker-ubuntu-22-04","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/11\/install-docker-ubuntu-22-04\/","title":{"rendered":"How to Install Docker on Ubuntu 22.04 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This guide shows you how to install docker on ubuntu 22.04 using Docker\u2019s maintained APT repository. In roughly ten minutes, you will have Docker Engine, Compose v2, non-root CLI access, and a private web-container test ready on your VPS.<\/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 22.04 LTS VPS, preferably 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 regular account that can run commands with <code>sudo<\/code>.<\/li>\n\n\n<li>Outbound HTTPS access to Docker\u2019s package repository and Docker Hub.<\/li>\n\n\n<li>UFW may be enabled, but no domain is required for this installation or its loopback-only test. You need a domain later for a public application with HTTPS.<\/li>\n\n\n<li>A <a href=\"https:\/\/vpslake.com\/buy-vps\">VPSLake Linux VPS<\/a> if you need an Ubuntu server with root access for containers.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The commands below assume a normal SSH account and therefore use <code>sudo<\/code>. If you are already root, remove <code>sudo<\/code>. Ubuntu 22.04 uses APT and UFW; do not substitute DNF, firewalld, or instructions for another Ubuntu release.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Jammy and update the VPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release before adding a repository prevents packages for a different Ubuntu codename from entering the system.<\/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 upgrade -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The output should identify Ubuntu 22.04 and the <code>jammy<\/code> codename. Docker supports several 64-bit Ubuntu architectures; <code>amd64<\/code> is common on VPS plans:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Ubuntu 22.04.5 LTS\"\nVERSION_ID=\"22.04\"\nVERSION_CODENAME=jammy\namd64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Resolve any DNS, mirror, or interrupted-package error before continuing. A clean package index makes the repository and dependency checks easier to diagnose.<\/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\u2019s <code>docker.io<\/code> and older Compose packages are distribution builds, while Docker\u2019s official packages provide their own Engine, containerd, Buildx, and Compose components.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a new VPS, remove possible conflicts:<\/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 were not installed. That is normal. If this is an existing Docker host, inspect its containers and images first: removing packages does not automatically erase <code>\/var\/lib\/docker\/<\/code>, but replacing a working runtime can still interrupt services.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker\u2019s signed Jammy repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The official repository gives APT a maintained source for Docker Engine and lets normal package updates handle future releases.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the certificate and download tools, store Docker\u2019s signing key with readable permissions, and create an APT source specifically for Jammy:<\/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: jammy\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 a candidate from Docker\u2019s 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 source should mention <code>download.docker.com<\/code> and <code>jammy\/stable<\/code>, with a current version listed as the candidate:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Candidate: 5:29.x.y-1~ubuntu.22.04~jammy\n 500 https:\/\/download.docker.com\/linux\/ubuntu jammy\/stable amd64 Packages<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For the repository layout and supported Ubuntu releases, see Docker\u2019s <a href=\"https:\/\/docs.docker.com\/engine\/install\/ubuntu\/\" target=\"_blank\" rel=\"noopener\">official Ubuntu installation guide<\/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 its companion plugins together keeps the runtime, CLI, 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\">Check both systemd state and the installed command-line tools:<\/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\">Successful output looks like this; patch versions change as Docker publishes updates:<\/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 a disposable Docker smoke test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The official <code>hello-world<\/code> image checks the full 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 confirmation below:<\/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 its message. <code>--rm<\/code> removes the stopped container record, although the small image remains cached for the next command.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Enable Docker for one non-root account<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker\u2019s Unix socket is root-owned by default. Adding a trusted administrator to the <code>docker<\/code> group avoids repeated <code>sudo<\/code>, but it also grants practical root-level control of the VPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the regular 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\">Log out of SSH completely and reconnect so the new group membership is loaded. 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 include <code>docker<\/code>, and the second command should print the Engine version. <code>newgrp docker<\/code> can reload the group in the current shell, but a new SSH session is less error-prone.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Check container networking without opening a public port<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A local NGINX container proves that Docker can publish a port while binding it only to loopback, so neither a DNS record nor an inbound firewall rule is needed.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name jammy-web-check --publish 127.0.0.1:18080:80 nginx:alpine\ndocker ps --filter name=jammy-web-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\njammy-web-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 you are done:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker stop jammy-web-check\ndocker rm jammy-web-check<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Ubuntu 22.04<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run this final check after reconnecting as your Docker administrator. It confirms 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\">Expected results include <code>active<\/code>, <code>enabled<\/code>, 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<p class=\"wp-block-paragraph\">For Docker\u2019s interaction with UFW and published ports, 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 Ubuntu 22.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\">Run Docker on Ubuntu 22.04<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get a root-access Ubuntu VPS for Docker apps, Compose projects, and development 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>docker-ce<\/code> has no installation candidate<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">APT has not loaded the Docker repository, or the source file uses a suite other than <code>jammy<\/code>. Inspect 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 source should show <code>Suites: jammy<\/code>, and the policy output should offer a candidate from <code>download.docker.com<\/code>. Recreate Step 3 if the file is missing or points to another release.<\/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 assigned to the Docker source. Download the key again and ensure it is world-readable; do not bypass signature verification or mark the source 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 current SSH session started before the account joined the <code>docker<\/code> group, or the group command was run for another user. Reconnect, run <code>id -nG<\/code>, and retry; <code>sudo docker info<\/code> is only a temporary diagnostic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>curl: (7) Failed to connect to 127.0.0.1 port 18080<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The NGINX image may still be downloading, the test container may have stopped, or another local process may own the port. Inspect the container and listener:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker ps -a --filter name=jammy-web-check\ndocker logs jammy-web-check\nsudo ss -ltnp | grep ':18080'<\/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:28080:80<\/code>, then request <code>http:\/\/127.0.0.1:28080<\/code>. A loopback binding does not need a 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 on its Unix socket. Never expose an unauthenticated Docker API on TCP port 2375; remote administration needs deliberate TLS configuration and narrow source-IP rules.<\/li>\n\n\n<li>Treat membership in the <code>docker<\/code> group as administrative access. Use rootless Docker when you need a stronger privilege boundary.<\/li>\n\n\n<li>Keep application ports on <code>127.0.0.1<\/code> until a TLS reverse proxy is ready. Docker-published ports can bypass ordinary UFW filtering, so review Docker\u2019s <code>DOCKER-USER<\/code> chain before exposing a service. 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>If you publish a web application, allow only the ports you intend to use and keep SSH restricted to your administration source addresses. Add the matching UFW rules before exposing the container, then verify them with <code>sudo ufw status numbered<\/code>.<\/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 Ubuntu 22.04 still supported by Docker?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes. Docker\u2019s current Ubuntu instructions list Jammy 22.04 LTS as a supported release. Use the <code>jammy<\/code> suite in the repository definition even when the server has a newer minor point release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need Docker Desktop on an Ubuntu VPS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. Docker Engine is the server-side runtime intended for Linux VPS deployments, and it runs as a system service without a graphical desktop. Docker Desktop is a separate product aimed at desktop workflows.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why does <code>docker compose<\/code> work but <code>docker-compose<\/code> does not?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This guide installs the Compose v2 plugin, whose current command is <code>docker compose<\/code> with a space. The older hyphenated <code>docker-compose<\/code> command is a separate legacy package and is not required for new installations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does Docker need a public firewall port?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. The daemon uses a local Unix socket, and the verification container binds to <code>127.0.0.1<\/code>. Open only the application ports you intentionally publish, and account for Docker\u2019s packet-filtering behavior rather than assuming UFW alone controls every container port.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Is Ubuntu 22.04 still supported by Docker?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes. Docker\u2019s current Ubuntu instructions list Jammy 22.04 LTS as a supported release. Use the `jammy` suite in the repository definition even when the server has a newer minor point release.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need Docker Desktop on an Ubuntu VPS?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. Docker Engine is the server-side runtime intended for Linux VPS deployments, and it runs as a system service without a graphical desktop. Docker Desktop is a separate product aimed at desktop workflows.\"}}, {\"@type\": \"Question\", \"name\": \"Why does `docker compose` work but `docker-compose` does not?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"This guide installs the Compose v2 plugin, whose current command is `docker compose` with a space. The older hyphenated `docker-compose` command is a separate legacy package and is not required for new installations.\"}}, {\"@type\": \"Question\", \"name\": \"Does Docker need a public firewall port?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. The daemon uses a local Unix socket, and the verification container binds to `127.0.0.1`. Open only the application ports you intentionally publish, and account for Docker\u2019s packet-filtering behavior rather than assuming UFW alone controls every container port.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Ubuntu 22.04 with Docker Engine, Compose v2, non-root access, firewall guidance, a local test, and fixes for common VPS errors.","protected":false},"author":1,"featured_media":1058,"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,101],"class_list":["post-1057","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-docker","tag-docker-compose","tag-ubuntu-22-04","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1057","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=1057"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1057\/revisions"}],"predecessor-version":[{"id":3985,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1057\/revisions\/3985"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1058"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1057"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1057"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}