{"id":1061,"date":"2026-11-11T18:00:00","date_gmt":"2026-11-11T18:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1061"},"modified":"2026-11-11T18:00:00","modified_gmt":"2026-11-11T18:00:00","slug":"install-docker-ubuntu-25-10","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/11\/install-docker-ubuntu-25-10\/","title":{"rendered":"How to Install Docker on Ubuntu 25.10 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker on Ubuntu 25.10 from Docker\u2019s signed APT repository and finish with Compose v2, non-root CLI access, and a working container. A fresh VPS takes about ten minutes, and the test service remains private to the server.<\/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 25.10 (Questing) 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 regular account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>Outbound HTTPS access to Docker\u2019s package repository and Docker Hub.<\/li>\n\n\n<li>No domain is needed for installation or the local test. You need a domain later if a public web application will use HTTPS.<\/li>\n\n\n<li>A <a href=\"https:\/\/vpslake.com\/buy-vps\">VPSLake Linux VPS<\/a> if you need a root-access Ubuntu server for Docker workloads.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These commands use <code>sudo<\/code> for a normal SSH account. If you are logged in as <code>root<\/code>, omit <code>sudo<\/code>. Ubuntu uses APT and UFW; do not substitute DNF or firewalld commands from an Enterprise Linux guide.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Ubuntu 25.10 and update the VPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release and architecture first prevents you from configuring the Docker source for the wrong Ubuntu suite.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME|UBUNTU_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 output should identify Ubuntu 25.10, use the <code>questing<\/code> codename, and normally report <code>amd64<\/code> on a VPS:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Ubuntu 25.10\"\nVERSION_ID=\"25.10\"\nVERSION_CODENAME=questing\nUBUNTU_CODENAME=questing\namd64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The minor package updates will vary. Resolve any DNS, mirror, or interrupted-package error before changing the Docker repositories.<\/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> package, its Compose 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 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 one or more packages are not installed. That is harmless. This command does not remove images, containers, volumes, or networks already stored under <code>\/var\/lib\/docker\/<\/code>, so inspect existing workloads before changing an active Docker host.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker\u2019s signed Questing 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, store Docker\u2019s key in APT\u2019s keyring directory, and create a deb822 source file. The command reads Ubuntu\u2019s codename instead of hard-coding a different release:<\/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\">Confirm that APT sees a Docker Engine candidate from the <code>questing<\/code> repository:<\/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 reference <code>download.docker.com<\/code> and an Ubuntu 25.10\/Questing package build. Docker\u2019s <a href=\"https:\/\/docs.docker.com\/engine\/install\/ubuntu\/\" target=\"_blank\" rel=\"noopener\">Ubuntu installation documentation<\/a> describes this repository format and package set.<\/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 client, runtime, image builder, and Compose integration aligned.<\/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 the service and both Docker command-line components:<\/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 includes an active, enabled service and version strings. Docker\u2019s patch numbers change as new builds are published:<\/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 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, look for this 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 container exits after printing its message. <code>--rm<\/code> removes the stopped container record, while 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\">Docker\u2019s Unix socket is root-owned by default. The <code>docker<\/code> group avoids repeated <code>sudo<\/code>, but membership grants root-equivalent control over the VPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the normal SSH account that should manage containers. Replace <code>YOUR_LINUX_USER<\/code> with that account\u2019s login name:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd --force docker\nsudo usermod -aG docker YOUR_LINUX_USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Log out of SSH completely and reconnect as that user so the new group is loaded. Then test 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. Use <code>newgrp docker<\/code> for a temporary current-shell refresh if reconnecting is inconvenient.<\/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 while binding only to the VPS itself, so you do not need a public firewall rule or DNS record.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name ubuntu2510-docker-check --publish 127.0.0.1:18085:80 nginx:alpine\ndocker ps --filter name=ubuntu2510-docker-check --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'\ncurl --head --max-time 10 http:\/\/127.0.0.1:18085<\/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\nubuntu2510-docker-check  Up ...        127.0.0.1:18085-&gt;80\/tcp\nHTTP\/1.1 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the disposable test after checking it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker stop ubuntu2510-docker-check\ndocker rm ubuntu2510-docker-check<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Ubuntu 25.10<\/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<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\">The result should contain <code>active<\/code>, <code>enabled<\/code>, Docker 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 published ports and their interaction 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 Ubuntu 25.10\">\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 25.10<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get a root-access Linux VPS for containers, 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>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 other than <code>questing<\/code>. Check the release, source, and candidate:<\/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 must show <code>Suites: questing<\/code>, and the policy output should show 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 <code>The repository ... is not signed<\/code><\/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 restore its readable permission before refreshing package metadata:<\/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<p class=\"wp-block-paragraph\">Do not bypass signature checks or mark the source as trusted; APT\u2019s signature validation is what protects the package 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\">The current SSH session was opened before the account joined the <code>docker<\/code> group, or the group command targeted another user. Reconnect, run <code>id -nG<\/code>, and retry. <code>sudo docker info<\/code> is only a temporary diagnostic while you correct the account setup.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>curl: (7) Failed to connect to 127.0.0.1 port 18085<\/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 own port 18085. Inspect the container and local listener:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker ps -a --filter name=ubuntu2510-docker-check\ndocker logs ubuntu2510-docker-check\nsudo ss -ltnp 'sport = :18085'<\/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:28085:80<\/code>, then request <code>http:\/\/127.0.0.1:28085<\/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\u2019s API 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 Docker rootless mode when the workload needs a stronger privilege boundary.<\/li>\n\n\n<li>Keep internal services on <code>127.0.0.1<\/code> until they sit behind a TLS reverse proxy. If an application must be public, allow only SSH, HTTP, and HTTPS in UFW, and review Docker\u2019s <code>DOCKER-USER<\/code> chain because published container ports can bypass ordinary UFW filtering. 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>Monitor <code>\/var\/lib\/docker<\/code> and container logs. Image layers and the default JSON logging 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\">Why does the repository use <code>questing<\/code>?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu 25.10\u2019s codename is Questing, and Docker publishes Ubuntu 25.10 package builds under that suite. The deb822 source in Step 3 reads the codename from the operating system so APT selects matching repository metadata.<\/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 headless daemon and CLI intended for server workloads. Docker Desktop is a separate desktop application and is not needed for containers managed over SSH.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need to open port 18085 in UFW?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. The NGINX test binds to <code>127.0.0.1<\/code>, so it accepts connections only from the VPS itself. When you later publish a real application, expose only its required ports and review Docker\u2019s firewall behavior before relying on UFW rules alone.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where does Docker store images and containers?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Docker normally stores its data under <code>\/var\/lib\/docker<\/code>, with runtime data under <code>\/var\/lib\/containerd<\/code>. Leave enough space on that filesystem for image layers, writable container data, and logs, and inspect volumes before pruning anything.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Why does the repository use `questing`?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Ubuntu 25.10\u2019s codename is Questing, and Docker publishes Ubuntu 25.10 package builds under that suite. The deb822 source in Step 3 reads the codename from the operating system so APT selects matching repository metadata.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need Docker Desktop on an Ubuntu VPS?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. Docker Engine is the headless daemon and CLI intended for server workloads. Docker Desktop is a separate desktop application and is not needed for containers managed over SSH.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need to open port 18085 in UFW?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. The NGINX test binds to `127.0.0.1`, so it accepts connections only from the VPS itself. When you later publish a real application, expose only its required ports and review Docker\u2019s firewall behavior before relying on UFW rules alone.\"}}, {\"@type\": \"Question\", \"name\": \"Where does Docker store images and containers?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Docker normally stores its data under `\/var\/lib\/docker`, with runtime data under `\/var\/lib\/containerd`. Leave enough space on that filesystem for image layers, writable container data, and logs, and inspect volumes before pruning anything.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Ubuntu 25.10 from Docker\u2019s signed APT repository, enable Compose v2, run a safe container test, and fix common VPS errors fast.","protected":false},"author":1,"featured_media":1062,"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,103],"class_list":["post-1061","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-docker","tag-docker-compose","tag-ubuntu-25-10","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1061","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=1061"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1061\/revisions"}],"predecessor-version":[{"id":3991,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1061\/revisions\/3991"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1062"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1061"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1061"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1061"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}