{"id":1005,"date":"2026-11-04T18:00:00","date_gmt":"2026-11-04T18:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1005"},"modified":"2026-11-04T18:00:00","modified_gmt":"2026-11-04T18:00:00","slug":"install-docker-compose-ubuntu-25-10","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/04\/install-docker-compose-ubuntu-25-10\/","title":{"rendered":"How to Install Docker Compose on Ubuntu 25.10"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker Compose on Ubuntu 25.10 with Ubuntu\u2019s packaged Docker Engine and Compose v2 plugin, then launch a two-service test stack in about ten minutes. You will finish with a repeatable <code>compose.yaml<\/code> project whose web port stays private to the 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 25.10 (Questing Quokka) 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 normal account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>Outbound HTTPS access to Ubuntu package mirrors and Docker Hub.<\/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.<\/li>\n\n\n<li>No domain is needed for this local test. Add a domain later when you put a public application behind a TLS reverse proxy.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The commands use <code>sudo<\/code> for a regular SSH account. If you are logged in as <code>root<\/code>, remove <code>sudo<\/code>. Ubuntu 25.10 is an interim release; for a new production VPS, choose a supported LTS release instead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Questing and refresh APT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the codename first prevents you from installing packages from the wrong Ubuntu release.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(PRETTY_NAME|VERSION_CODENAME)=' \/etc\/os-release\ndpkg --print-architecture\nsudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The release should report <code>VERSION_CODENAME=questing<\/code> and a supported architecture such as <code>amd64<\/code> or <code>arm64<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"Ubuntu 25.10 (Questing Quokka)\"\nVERSION_CODENAME=questing\namd64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu 25.10 is not currently listed among Docker\u2019s supported releases in Docker\u2019s <a href=\"https:\/\/docs.docker.com\/engine\/install\/ubuntu\/\" target=\"_blank\" rel=\"noopener\">Ubuntu Engine installation guide<\/a>. Do not change <code>questing<\/code> to <code>noble<\/code> in a Docker source file: mixing release repositories can replace core dependencies with incompatible versions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Install Ubuntu\u2019s Docker Engine and Compose v2 packages<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu maintains <code>docker.io<\/code> and the <code>docker-compose-v2<\/code> plugin in its own repositories, so this release does not need Docker\u2019s third-party APT repository.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y 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\">The service check should print <code>active<\/code>, followed by Docker client\/server versions and a Compose v2 version. Package versions change as Ubuntu publishes updates:<\/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 Docker\u2019s small smoke test before creating a project. It downloads an image, runs a container, prints a confirmation, and then exits:<\/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\">The output should include:<\/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<h2 class=\"wp-block-heading\">Step 3: Let your SSH user run Docker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The daemon socket is root-owned by default. Adding your account to the <code>docker<\/code> group avoids <code>sudo<\/code> on every Compose command, but that group grants root-equivalent control of the server.<\/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\">Disconnect and open a new SSH session so the group change is loaded. Then check 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 Docker server version. Use <code>sudo docker compose ...<\/code> until you reconnect, or run <code>newgrp docker<\/code> to start a shell with the new membership. Treat this group as administrative access; use rootless Docker when you need a stronger boundary.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Create a Compose project<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Compose keeps service definitions, configuration, and persistent storage in one file. This example serves a static page with Caddy and starts PostgreSQL on Compose\u2019s private network; neither service is exposed on a public interface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create the project directory and page:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p ~\/compose-questing\/site\ncd ~\/compose-questing\nprintf '%s\\n' 'Docker Compose on Ubuntu 25.10 is running.' &gt; site\/index.html<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a Caddy configuration that serves that directory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tee Caddyfile &gt;\/dev\/null &lt;&lt;'EOF'\n:80 {\n    root * \/srv\n    file_server\n}\nEOF<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a protected environment file. Replace the placeholder with a long, unique password before starting the stack; it is not a usable credential:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tee .env &gt;\/dev\/null &lt;&lt;'EOF'\nPOSTGRES_PASSWORD=YOUR_STRONG_POSTGRES_PASSWORD\nEOF\nchmod 600 .env<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now create <code>compose.yaml<\/code>. The database has no host port, so only the web container can reach it through the private Compose network.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tee compose.yaml &gt;\/dev\/null &lt;&lt;'YAML'\nservices:\n  web:\n    image: caddy:2-alpine\n    ports:\n      - \"127.0.0.1:8089:80\"\n    volumes:\n      - .\/site:\/srv:ro\n      - .\/Caddyfile:\/etc\/caddy\/Caddyfile:ro\n    depends_on:\n      db:\n        condition: service_healthy\n\n  db:\n    image: postgres:17-alpine\n    environment:\n      POSTGRES_DB: compose_demo\n      POSTGRES_USER: compose_app\n      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}\n    volumes:\n      - postgres_data:\/var\/lib\/postgresql\/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U compose_app -d compose_demo\"]\n      interval: 5s\n      timeout: 5s\n      retries: 12\n\nvolumes:\n  postgres_data:\nYAML<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The read-only site mount protects the host files, while the named volume keeps PostgreSQL data when its container is recreated. The <code>depends_on<\/code> condition makes Caddy wait until PostgreSQL reports healthy; it does not publish or otherwise secure a database for internet access. Docker\u2019s <a href=\"https:\/\/docs.docker.com\/reference\/compose-file\/\" target=\"_blank\" rel=\"noopener\">Compose file reference<\/a> explains the service, volume, and health-check keys used here.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Validate and start the stack<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Render the file before pulling images so YAML errors, missing variables, and indentation mistakes fail early.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose config --quiet\ndocker compose pull\ndocker compose up -d\ndocker compose ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>config --quiet<\/code> should produce no output and return status 0. After the first image download, both services should be running and the database should be healthy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>NAME                   SERVICE   STATUS                   PORTS\ncompose-questing-web-1 web       Up                       127.0.0.1:8089-&gt;80\/tcp\ncompose-questing-db-1  db        Up (healthy)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Container names can vary if you choose a different directory. Use the <code>SERVICE<\/code> column, such as <code>web<\/code> or <code>db<\/code>, in later commands.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker Compose on Ubuntu 25.10<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Check the page, query PostgreSQL\u2019s readiness probe, and review recent logs. Together, these checks prove that Compose rendered the file, started both services, and kept the database private.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl --fail --silent --show-error http:\/\/127.0.0.1:8089\ndocker compose exec db pg_isready -U compose_app -d compose_demo\ndocker compose logs --tail 20 web db<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first two commands should return the page text and a readiness message:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Docker Compose on Ubuntu 25.10 is running.\n\/var\/run\/postgresql:5432 - accepting connections<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Port 8089 is bound to the VPS loopback address, so a browser on your workstation cannot reach it directly. For private browser testing, create an SSH tunnel from your workstation with <code>ssh -L 8089:127.0.0.1:8089 USER@SERVER_IP<\/code>, then open <code>http:\/\/127.0.0.1:8089<\/code> locally. If you later publish an application, use TLS and a deliberate Docker-aware firewall policy; Docker documents that published container ports can bypass ordinary UFW filtering. VPSLake\u2019s <a href=\"https:\/\/vpslake.com\/ufw-firewall-generator\">UFW firewall generator<\/a> can help draft the host rules.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Run Compose on Questing\">\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 Compose on Questing<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Launch isolated Docker Compose services on an Ubuntu VPS with root access.<\/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>Package 'docker-compose-v2' has no installation candidate<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Universe component may be disabled, or APT may still have stale metadata. Enable the component and retry:<\/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 APT instead reports that a Questing mirror returns 404, see the next problem. Do not install a Noble package to work around a missing Questing index.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>404 Not Found<\/code> or <code>does not have a Release file<\/code> for Questing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu 25.10 is an interim release, and its standard support ended in July 2026. Normal mirrors may no longer carry its packages. For a production server, migrate to a supported Ubuntu LTS image; do not point a Questing host at Noble or Resolute repositories. If this is an existing machine you must preserve, follow Ubuntu\u2019s release-upgrade guidance before continuing.<\/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\">Your current SSH session has not loaded the new group membership, or the command is running as a different user. Reconnect, run <code>id -nG<\/code>, and retry. <code>sudo docker compose ...<\/code> is a temporary workaround, but remember that <code>docker<\/code> group membership is effectively root access.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>dependency failed to start<\/code> or <code>db is unhealthy<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The database may still be initializing, or the <code>.env<\/code> file still contains the placeholder password. Check the value and inspect the service log:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -n '^POSTGRES_PASSWORD=' .env\ndocker compose logs db\ndocker compose ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Replace <code>YOUR_STRONG_POSTGRES_PASSWORD<\/code> with a real value containing no spaces around the equals sign, then run <code>docker compose up -d<\/code> again. For this disposable example only, <code>docker compose down -v<\/code> removes the named database volume; never use <code>-v<\/code> when it contains data you need.<\/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 application ports on <code>127.0.0.1<\/code> until a TLS reverse proxy, authentication, and Docker-aware firewall policy are ready.<\/li>\n\n\n<li>Never expose an unauthenticated Docker API on port 2375 or 2376.<\/li>\n\n\n<li>Back up <code>postgres_data<\/code> and record the <code>.env<\/code> secret in a password manager; do not commit either file to a public repository.<\/li>\n\n\n<li>Update images deliberately with <code>docker compose pull<\/code> and <code>docker compose up -d<\/code>, and pin reviewed image tags or digests for production.<\/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 this guide use Ubuntu packages instead of Docker\u2019s APT repository?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Docker\u2019s current Ubuntu installation page lists supported LTS releases, not Ubuntu 25.10. Ubuntu\u2019s <code>docker.io<\/code> and <code>docker-compose-v2<\/code> packages are built for Questing, so using them avoids pretending that a different release is supported.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should I use <code>docker compose<\/code> or <code>docker-compose<\/code>?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use <code>docker compose<\/code> with a space. It is Compose v2 provided as a Docker CLI plugin; the hyphenated <code>docker-compose<\/code> command is the older standalone interface and is not needed here.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does Compose create a new VPS or virtual machine?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. Compose creates and manages containers, networks, and volumes on the Docker host. The <code>compose.yaml<\/code> file describes those resources so you can recreate the application consistently on another Docker server.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I stop the example without deleting PostgreSQL data?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run <code>docker compose down<\/code> from <code>~\/compose-questing<\/code>; it removes the containers and network but leaves the named volume. Avoid <code>docker compose down -v<\/code> unless you intentionally want to erase the example database.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Why does this guide use Ubuntu packages instead of Docker\u2019s APT repository?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Docker\u2019s current Ubuntu installation page lists supported LTS releases, not Ubuntu 25.10. Ubuntu\u2019s `docker.io` and `docker-compose-v2` packages are built for Questing, so using them avoids pretending that a different release is supported.\"}}, {\"@type\": \"Question\", \"name\": \"Should I use `docker compose` or `docker-compose`?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Use `docker compose` with a space. It is Compose v2 provided as a Docker CLI plugin; the hyphenated `docker-compose` command is the older standalone interface and is not needed here.\"}}, {\"@type\": \"Question\", \"name\": \"Does Compose create a new VPS or virtual machine?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. Compose creates and manages containers, networks, and volumes on the Docker host. The `compose.yaml` file describes those resources so you can recreate the application consistently on another Docker server.\"}}, {\"@type\": \"Question\", \"name\": \"How do I stop the example without deleting PostgreSQL data?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Run `docker compose down` from `~\/compose-questing`; it removes the containers and network but leaves the named volume. Avoid `docker compose down -v` unless you intentionally want to erase the example database.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker Compose on Ubuntu 25.10 with Ubuntu&#8217;s Docker packages, run a two-service stack, verify it, and fix common setup errors quickly.","protected":false},"author":1,"featured_media":1006,"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-1005","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\/1005","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=1005"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1005\/revisions"}],"predecessor-version":[{"id":3889,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1005\/revisions\/3889"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1006"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1005"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1005"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1005"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}