How to Install Docker Compose on Ubuntu 26.04

Install Docker Compose on Ubuntu 26.04 with Docker’s signed APT repository, run a private two-service stack, verify it, and fix setup errors fast.
A polished 3D illustration of glowing container blocks beside a server rack on a midnight-blue background with orange accents.

Install Docker Compose on Ubuntu 26.04 by configuring Docker’s official repository, adding the Compose v2 plugin, and running a small reverse-proxy stack. In about ten minutes, you will have a repeatable compose.yaml project whose test service is reachable only from the VPS.

Prerequisites

  • A 64-bit Ubuntu 26.04 LTS (Resolute Raccoon) VPS with at least 2 GB of RAM and 10 GB of free disk space.
  • SSH access as root, or a regular account with working sudo privileges.
  • Outbound HTTPS access to Docker’s APT repository and Docker Hub.
  • A VPSLake Linux VPS if you need an Ubuntu server with root access.
  • No domain is needed for this local test. Use a domain later if you put a public service behind a TLS reverse proxy.

The commands assume a sudo-enabled SSH user. If you are logged in as root, remove sudo. The docker group grants root-equivalent control, so add only trusted administrator accounts.

Step 1: Confirm Resolute and refresh APT

Docker publishes packages for specific Ubuntu releases, so check the codename before adding a repository.

grep -E '^(PRETTY_NAME|VERSION_CODENAME)=' /etc/os-release
dpkg --print-architecture
sudo apt update

The release check should report Ubuntu 26.04 and resolute, with an architecture such as amd64 or arm64:

PRETTY_NAME="Ubuntu 26.04.1 LTS"
VERSION_CODENAME=resolute
amd64

If apt update reports a DNS, mirror, or signature error, fix that before continuing. Docker’s packages cannot be installed reliably while the base APT configuration is unhealthy.

Step 2: Add Docker’s signed Ubuntu repository

The keyring and deb822 source file let APT verify Docker packages without the deprecated apt-key workflow. The command reads Ubuntu’s codename from the machine, so it cannot accidentally use a Noble or Jammy source on Resolute.

sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update

Confirm that APT sees an Engine candidate from Docker’s repository:

apt-cache policy docker-ce

The output should show a Candidate version and a source containing download.docker.com and resolute. Docker’s Ubuntu Engine installation guide lists Resolute as supported and documents this repository method.

Step 3: Install Docker Engine and Compose v2

Compose v2 is a Docker CLI plugin. Installing it with the Engine gives you the maintained docker compose command instead of the legacy docker-compose executable.

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo systemctl is-active docker
sudo docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
sudo docker compose version

You should see an active daemon, Docker client and server versions, and a Compose v2 version. Exact patch numbers change as packages are updated:

active
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y

Run Docker’s disposable smoke test before creating a project:

sudo docker run --rm hello-world

The output includes a confirmation similar to this:

Hello from Docker!
Your installation appears to be working correctly.

If this VPS already has docker.io, docker-compose-v2, or a standalone docker-compose installation, identify existing workloads before replacing packages. Removing a package does not automatically remove /var/lib/docker, but an unplanned package migration can still interrupt services.

Step 4: Allow your SSH user to run Docker

Docker’s Unix socket is root-owned by default. The docker group removes repeated sudo prompts, but anyone in it can effectively administer the host through Docker.

sudo groupadd --force docker
sudo usermod -aG docker "$USER"

Disconnect and open a new SSH session so the group membership is loaded, then test access:

id -nG
docker info --format 'Server {{.ServerVersion}}'

The first command should include docker, and the second should print the Engine version. Until reconnecting, use sudo docker compose ... or run newgrp docker in the current shell. Rootless Docker is a better choice when this administrative boundary is too broad.

Step 5: Build a private two-service Compose project

The example uses Caddy as a reverse proxy in front of a small HTTP application. Both services share Compose’s private network, while Caddy is published only on the VPS loopback interface; this demonstrates service discovery without exposing a test app to the internet.

Create the project directory:

mkdir -p ~/compose-resolute
cd ~/compose-resolute

Create a Caddyfile. The name app is the Compose service name, not an IP address; Compose resolves it on the project network.

tee Caddyfile >/dev/null <<'EOF'
:80 {
    reverse_proxy app:80
}
EOF

Now create compose.yaml:

services:
  proxy:
    image: caddy:2-alpine
    ports:
      - "127.0.0.1:8092:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - app

  app:
    image: traefik/whoami
    expose:
      - "80"

volumes:
  caddy_data:
  caddy_config:

The read-only Caddyfile mount protects the host configuration. The expose entry documents the app’s internal port but does not publish it; only proxy has a host mapping, and 127.0.0.1 means remote clients cannot connect directly. Caddy’s reverse proxy documentation explains the directive used here.

Step 6: Validate and start the services

Render the file first so YAML and service-name mistakes fail before images are downloaded.

docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps

config --quiet should return no output and exit successfully. After the first pull, both services should show Up or running:

NAME                    SERVICE   STATUS       PORTS
compose-resolute-proxy-1 proxy     Up           127.0.0.1:8092->80/tcp
compose-resolute-app-1   app       Up

Container names follow the project directory and may differ if you choose another path. Use the SERVICE column, such as proxy or app, in later commands. Docker’s Compose file reference covers the service, volume, network, and port keys used here.

Verify Docker Compose on Ubuntu 26.04

Query the proxy locally and validate Caddy’s loaded configuration. The response proves that the request crossed the Compose network from Caddy to the app container.

curl --fail --silent --show-error http://127.0.0.1:8092 | grep -E '^(Hostname|IP:)'
docker compose exec proxy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
docker compose logs --tail 20 proxy app

Expected output includes an application hostname, one or more container IP addresses, and a successful Caddy validation message:

Hostname: 4f2c9b7d1a20
IP: 127.0.0.1
Valid configuration

The hostname and IP values are generated at runtime, so your output will differ. Port 8092 is bound to loopback and does not need a UFW allow rule. If you later publish a service on 80 or 443, allow only those ports, add TLS, and account for Docker’s published-port behavior; VPSLake’s UFW firewall generator can help draft the host rules.

Troubleshooting

docker: 'compose' is not a docker command

The CLI plugin is missing, or the shell is using a different Docker installation. Refresh the package index and install the plugin from the configured Docker repository:

sudo apt update
sudo apt install -y docker-compose-plugin
docker compose version

Use a space in docker compose. The hyphenated command is the legacy standalone interface and is not required here.

E: Package 'docker-ce' has no installation candidate

APT is not reading the Resolute repository, commonly because the source file was edited with the wrong suite or apt update failed. Inspect and refresh it:

cat /etc/apt/sources.list.d/docker.sources
sudo apt update
apt-cache policy docker-ce

The file should contain Suites: resolute, and the update must finish without errors. Do not change the suite to noble just to obtain a package; mixing Ubuntu releases can replace core dependencies.

permission denied while trying to connect to the Docker daemon socket

The current SSH session has not loaded the new group membership, or the command is running as another account. Reconnect, run id -nG, and retry. sudo docker compose ... is a temporary workaround, but remember that docker group membership is administrative access.

502 Bad Gateway or connection refused on port 8092

The proxy may have started before the app was ready, the app container may have exited, or the Caddyfile may point to localhost. Inside the proxy container, localhost means the proxy itself; the upstream must be app:80.

docker compose ps
docker compose logs proxy app
docker compose up -d

If port 8092 is already occupied, change the left side of the mapping to an unused local port, such as 127.0.0.1:8093:80, and run docker compose up -d again.

Hardening

  • Keep Docker’s API on its Unix socket. Never expose an unauthenticated daemon on port 2375 or 2376.
  • Put a real public application behind HTTPS and authentication before changing the binding from 127.0.0.1 to a public interface.
  • Review image tags and pin production images to approved tags or digests instead of treating latest as a release process.
  • Remove the example cleanly with docker compose down; use docker compose down -v only when you intentionally want to delete the named Caddy volumes.

FAQ

Is Docker Compose included in Docker Engine on Ubuntu 26.04?

The Engine and Compose are separate packages in Docker’s APT repository. Installing docker-compose-plugin adds the docker compose subcommand, which you can confirm with docker compose version.

Why does the example use compose.yaml instead of docker-compose.yml?

Compose v2 recognizes both filenames, but compose.yaml is the current default for a new project. If a project uses another filename, pass it explicitly with docker compose -f FILE up -d.

Can I open port 8092 to access the demo from my laptop?

Do not publish the demo directly just for convenience. Use an SSH tunnel such as ssh -L 8092:127.0.0.1:8092 USER@SERVER_IP, then browse to http://127.0.0.1:8092 on your laptop; this preserves the loopback-only server binding.

Does docker compose down erase the Caddy volumes?

No. down removes the containers and network but keeps named volumes by default. Add -v only for this disposable example or after confirming that the volumes contain no certificates or other data you need.

Samrat Ghosh
Written by

Samrat Ghosh

Founder & Infrastructure Engineer

Samrat Ghosh is the founder of VPSLake, where he builds and runs the remote desktop and VPS hosting infrastructure the company is built on. He writes hands-on guides about RDP, Windows Server, VPS management and secure remote access - the practical documentation he wishes had…

Previous Article

Sasta RDP India UPI Se Kaise Kharidein (2026)

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨