{"id":1065,"date":"2026-11-12T09:00:00","date_gmt":"2026-11-12T09:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1065"},"modified":"2026-11-12T09:00:00","modified_gmt":"2026-11-12T09:00:00","slug":"install-docker-portainer-ubuntu-25-10","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/12\/install-docker-portainer-ubuntu-25-10\/","title":{"rendered":"How to Install Docker and Portainer on Ubuntu 25.10"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This guide shows you how to install Docker Portainer on Ubuntu 25.10, then manage the Docker host through Portainer CE\u2019s HTTPS dashboard in about ten minutes. Ubuntu 25.10 is archived now, so the package step uses Ubuntu\u2019s preserved Questing repository and includes a safer SSH-tunnel workflow for the web UI.<\/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 for images, containers, and Portainer data.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a regular Linux account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>Outbound HTTPS access to the Ubuntu archive and Docker Hub.<\/li>\n\n\n<li>An SSH client on your workstation for the local port-forward. A domain is not needed for this private setup; you need one later for a public HTTPS reverse proxy.<\/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\">Ubuntu 25.10 reached end of life in July 2026. The instructions below can still bring an existing Questing server online, but use Ubuntu 26.04 LTS for a new production deployment because archived packages do not receive normal security updates.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Questing and refresh the archived package sources<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release first prevents you from applying an archive workaround to the wrong Ubuntu version.<\/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 sed -i -E 's#https?:\/\/[^[:space:]]*ubuntu\\.com\/ubuntu#http:\/\/old-releases.ubuntu.com\/ubuntu#g' \/etc\/apt\/sources.list.d\/ubuntu.sources \/etc\/apt\/sources.list 2&gt;\/dev\/null || true\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first commands should identify Questing and a supported 64-bit architecture. The <code>sed<\/code> command changes only Ubuntu\u2019s standard source files to the archive host; it does not alter third-party repositories.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Ubuntu 25.10 (Questing Quokka)\"\nVERSION_ID=\"25.10\"\nVERSION_CODENAME=questing\namd64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If <code>apt update<\/code> reports that a repository has no Release file, do not replace <code>questing<\/code> with <code>noble<\/code> or another release. Follow the archive fix in Troubleshooting instead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Install Docker Engine and Compose v2 from Ubuntu<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker\u2019s <a href=\"https:\/\/docs.docker.com\/engine\/install\/ubuntu\/\" target=\"_blank\" rel=\"noopener\">official Ubuntu installation instructions<\/a> list Ubuntu 22.04, 24.04, and 26.04, not 25.10. Ubuntu\u2019s own <code>docker.io<\/code> and <code>docker-compose-v2<\/code> packages are the release-matched option for an archived Questing host.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y ca-certificates curl docker.io docker-compose-v2\nsudo systemctl enable --now docker\nsudo systemctl is-active 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\"><code>active<\/code> means systemd started the daemon, and the version commands should print client\/server and Compose v2 versions. Package revisions can differ from the example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nClient 29.x.y \/ Server 29.x.y\nDocker Compose version v2.x.y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run a small image to validate the daemon, registry access, and container runtime before adding Portainer:<\/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, Docker prints <code>Hello from Docker!<\/code> and confirms that the installation is working. The test container exits immediately and is removed by <code>--rm<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Allow your SSH user to manage Docker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Docker socket is root-owned by default, so adding one trusted account to the <code>docker<\/code> group removes repeated <code>sudo<\/code> prompts while keeping the daemon service unchanged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the regular sudo-enabled account that will administer the VPS:<\/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\">Close the SSH session and reconnect so the new group membership is loaded. Then check the socket 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. Membership is effectively administrative access, so do not add untrusted users to this group.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Deploy Portainer CE with a persistent data volume<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Portainer stores its users, settings, and environment configuration in a Docker volume; the volume lets you recreate the container without losing the dashboard configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The command below uses the maintained LTS image and binds HTTPS to loopback. It does not publish Portainer to the public network, and port 8000 is omitted because it is only needed for Portainer Edge Agent features.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker volume create portainer_data\ndocker run -d \\\n  --name portainer \\\n  --restart=always \\\n  --publish 127.0.0.1:9443:9443 \\\n  --volume \/var\/run\/docker.sock:\/var\/run\/docker.sock \\\n  --volume portainer_data:\/data \\\n  portainer\/portainer-ce:lts<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Portainer\u2019s <a href=\"https:\/\/docs.portainer.io\/start\/install-ce\/server\/docker\/linux\" target=\"_blank\" rel=\"noopener\">official Docker installation guide<\/a> uses the same server container, Docker socket mount, persistent volume, and HTTPS port. Portainer generates a self-signed certificate on first start, so a browser warning is expected for this initial local connection.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Open the dashboard through an SSH tunnel<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The loopback binding protects the administration panel from internet scans; an SSH tunnel carries only your workstation\u2019s browser traffic to the VPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this command on your local workstation, not inside the VPS shell. Replace the placeholders with your SSH account and server address, then leave the terminal running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh -N -L 9443:127.0.0.1:9443 YOUR_LINUX_USER@SERVER_IP<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Open <code>https:\/\/127.0.0.1:9443<\/code> in your browser. Accept the self-signed certificate warning for this private tunnel, create a strong unique administrator password, and choose <strong>Get Started<\/strong> on the initial setup page. The first setup window is time-limited, so complete it soon after starting the container.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker and Portainer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These checks prove that Docker is enabled at boot, Portainer is running, its HTTPS listener answers locally, and the restart policy is configured:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl is-active docker\nsystemctl is-enabled docker\ndocker ps --filter name=portainer --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.Ports}}'\ndocker inspect --format '{{.HostConfig.RestartPolicy.Name}}' portainer\ncurl --insecure --head --max-time 10 https:\/\/127.0.0.1:9443<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output has an active, enabled daemon, one running Portainer container, <code>always<\/code> as the restart policy, and an HTTP success response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nenabled\nNAMES      IMAGE                       STATUS       PORTS\nportainer  portainer\/portainer-ce:lts Up ...        127.0.0.1:9443-&gt;9443\/tcp\nalways\nHTTP\/1.1 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reconnect the SSH tunnel if you closed it, then reload <code>https:\/\/127.0.0.1:9443<\/code> to confirm that the administrator account and local Docker environment are available in the UI.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Run Docker and Portainer\">\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 and Portainer<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get a root-access Ubuntu VPS with Docker and Portainer ready for container management.<\/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>404 Not Found<\/code> or <code>does not have a Release file<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Questing has moved from the normal mirrors to Ubuntu\u2019s old-release archive. Inspect the source URLs and rerun the archive conversion from Step 1:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -RInE '^(URIs?:|deb )' \/etc\/apt\/sources.list.d\/ubuntu.sources \/etc\/apt\/sources.list 2&gt;\/dev\/null\nsudo sed -i -E 's#https?:\/\/[^[:space:]]*ubuntu\\.com\/ubuntu#http:\/\/old-releases.ubuntu.com\/ubuntu#g' \/etc\/apt\/sources.list.d\/ubuntu.sources \/etc\/apt\/sources.list 2&gt;\/dev\/null || true\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Ubuntu entries should point to <code>old-releases.ubuntu.com<\/code> and still use the <code>questing<\/code> suites. Do not mix packages from a different Ubuntu release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>Package 'docker-compose-v2' has no installation candidate<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>universe<\/code> component may not be enabled in the archived source configuration. Enable it, refresh APT, and repeat the package installation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y software-properties-common\nsudo add-apt-repository -y universe\nsudo apt update\nsudo apt install -y docker.io docker-compose-v2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the archive has been removed from your provider\u2019s image, reinstall the VPS with a supported Ubuntu LTS release rather than installing packages built for Noble or Resolute on Questing.<\/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 process started before the <code>docker<\/code> group was added, or a different login account was modified. Reconnect, run <code>id -nG<\/code>, and retry <code>docker info<\/code>; use <code>sudo docker info<\/code> only as a temporary diagnostic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The browser cannot connect to <code>127.0.0.1:9443<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Portainer is intentionally listening only on the VPS loopback interface. Start the tunnel from your workstation and keep it open:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh -N -L 9443:127.0.0.1:9443 YOUR_LINUX_USER@SERVER_IP<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On the VPS, <code>sudo ss -ltnp | grep ':9443'<\/code> should show a listener on <code>127.0.0.1:9443<\/code>. Do not add a UFW rule for this private design; there is no public Portainer port to open.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>Your Portainer instance has timed out for security purposes<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Portainer stops a fresh instance if initial setup is not completed within its security window. Restart it, reconnect the tunnel, and finish account creation promptly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker restart portainer<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the message returns after a restart, check <code>docker logs portainer<\/code> and confirm that the <code>portainer_data<\/code> volume is still mounted. Do not delete the volume unless you intentionally want to start over.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardening<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Treat the Docker socket mount and <code>docker<\/code> group as root-equivalent administration. Keep SSH keys protected and remove group membership from accounts that no longer manage the host.<\/li>\n\n\n<li>Keep Portainer on loopback for administration through SSH. For team access, put it behind a domain, a reverse proxy with a trusted TLS certificate, authentication controls, and narrowly scoped firewall rules.<\/li>\n\n\n<li>Do not expose Docker\u2019s unauthenticated TCP API on port 2375. Docker warns that published container ports can bypass ordinary UFW expectations, so review its packet-filtering and firewall guidance before publishing services.<\/li>\n\n\n<li>Back up the <code>portainer_data<\/code> volume before upgrades, and plan a move to Ubuntu 26.04 LTS because Questing is no longer receiving security updates.<\/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 use Docker\u2019s official APT repository on Ubuntu 25.10?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Not as a supported release path. Docker\u2019s current Ubuntu instructions list 22.04, 24.04, and 26.04, so this guide uses Ubuntu\u2019s own archived packages instead. Rebuild or upgrade the VPS to a supported LTS release before relying on regular Docker updates.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why does Portainer show a certificate warning?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A new Portainer CE installation creates a self-signed certificate for HTTPS on port 9443. The warning is expected when you connect through the private SSH tunnel; a public deployment should use a trusted certificate at a reverse proxy or provide a certificate through Portainer\u2019s documented configuration.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need to open port 8000 for this installation?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. Port 8000 is for the TCP tunnel used by Edge Agents, and the local Docker environment in this guide uses the mounted Unix socket. Leaving it unpublished reduces the exposed surface.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I access Portainer directly from the internet?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can, but a direct published Docker port needs deliberate network filtering because Docker\u2019s forwarding rules may not behave like ordinary UFW rules. A domain, trusted TLS certificate, reverse proxy, authentication policy, and a restricted source-IP rule are safer than exposing the raw Portainer port.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Can I use Docker\u2019s official APT repository on Ubuntu 25.10?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Not as a supported release path. Docker\u2019s current Ubuntu instructions list 22.04, 24.04, and 26.04, so this guide uses Ubuntu\u2019s own archived packages instead. Rebuild or upgrade the VPS to a supported LTS release before relying on regular Docker updates.\"}}, {\"@type\": \"Question\", \"name\": \"Why does Portainer show a certificate warning?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"A new Portainer CE installation creates a self-signed certificate for HTTPS on port 9443. The warning is expected when you connect through the private SSH tunnel; a public deployment should use a trusted certificate at a reverse proxy or provide a certificate through Portainer\u2019s documented configuration.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need to open port 8000 for this installation?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. Port 8000 is for the TCP tunnel used by Edge Agents, and the local Docker environment in this guide uses the mounted Unix socket. Leaving it unpublished reduces the exposed surface.\"}}, {\"@type\": \"Question\", \"name\": \"Can I access Portainer directly from the internet?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"You can, but a direct published Docker port needs deliberate network filtering because Docker\u2019s forwarding rules may not behave like ordinary UFW rules. A domain, trusted TLS certificate, reverse proxy, authentication policy, and a restricted source-IP rule are safer than exposing the raw Portainer port.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker Portainer on Ubuntu 25.10 with Ubuntu packages, a private HTTPS UI, Compose v2, verification, and common VPS fixes on a fresh VPS.","protected":false},"author":1,"featured_media":1066,"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,137,103],"class_list":["post-1065","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-docker","tag-portainer","tag-ubuntu-25-10","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1065","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=1065"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1065\/revisions"}],"predecessor-version":[{"id":4000,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1065\/revisions\/4000"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1066"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1065"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1065"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1065"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}