How to Install Docker Compose on Ubuntu 22.04

Install Docker Compose on Ubuntu 22.04 with Docker’s official repository, launch a two-service stack, verify it locally, and fix common errors.
A polished 3D illustration of container blocks beside a server rack on a midnight-blue background with orange Ubuntu-inspired accents.

Install Docker Compose on Ubuntu 22.04 by adding Docker’s signed APT repository, installing Compose v2, and launching a small Apache and MariaDB stack. You can reach a working local test in about ten minutes without exposing the database or web port to the public internet.

Prerequisites

  • A 64-bit Ubuntu 22.04 LTS (Jammy Jellyfish) 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 package repository and Docker Hub.
  • A VPSLake Linux VPS if you need an Ubuntu server with root access.
  • No domain is required for this private test. You will need a domain later if you place a public application behind a TLS reverse proxy.

The commands use sudo for a regular SSH account. If you are already logged in as root, remove sudo from each command.

Step 1: Confirm Jammy and refresh APT

Docker publishes a separate package repository for each Ubuntu release, so confirm that this server really is Ubuntu 22.04 before adding the Jammy source.

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

The release check should show Jammy and a supported architecture such as amd64 or arm64:

PRETTY_NAME="Ubuntu 22.04.5 LTS"
VERSION_CODENAME=jammy
amd64

Do not continue if apt update reports a broken mirror, DNS failure, or unsigned source. A clean package index is needed before Docker’s repository can be trusted.

Step 2: Add Docker’s signed Ubuntu repository

The keyring and deb822 source file let APT verify Docker packages without the deprecated apt-key command. This source is pinned to Ubuntu 22.04’s jammy suite.

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: jammy
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update

Check that APT can see a Docker Engine candidate from the new source:

apt-cache policy docker-ce

The Candidate should contain a version from download.docker.com, and the repository line should mention jammy. Docker’s Ubuntu installation guide lists Jammy as a supported release and documents this repository method.

Step 3: Install Docker Engine and Compose v2

Compose is maintained as a Docker CLI plugin, so install it with the Engine rather than downloading the old standalone docker-compose binary.

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, matching client and server versions, and a Compose v2 version. Patch numbers change over time, so do not compare the exact numbers:

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

Run Docker’s smoke test before creating a project. It downloads a small image, starts a container, prints a confirmation, and exits:

sudo docker run --rm hello-world

The output should contain:

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

If Docker was already installed, check docker compose version first. Do not mix an old docker.io or standalone docker-compose installation with Docker’s docker-ce packages until you have identified which installation owns your existing containers.

Step 4: Give your SSH user controlled Docker access

The Docker socket is root-owned by default. The docker group removes repeated sudo prompts, but membership is effectively administrative access to the VPS.

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

Disconnect from SSH and open a new session so the group membership is loaded. Then confirm that the account can reach the daemon:

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

The group list should include docker, followed by the Docker server version. Until you reconnect, use sudo docker compose ... or run newgrp docker for the current shell. For a stricter privilege boundary, use Docker’s rootless mode instead of adding a user to this group.

Step 5: Create an Apache and MariaDB Compose project

A Compose file keeps service definitions, the private network, and persistent storage together. This example serves a predictable page with Apache and starts MariaDB with a health check, while binding Apache only to the VPS loopback interface.

Create the project directory and its test page:

mkdir -p ~/compose-jammy/public
cd ~/compose-jammy
printf '%s\n' 'Docker Compose on Ubuntu 22.04 is running.' > public/index.html

Create a protected environment file. Replace both uppercase values with long, unique passwords before starting the stack; they are placeholders, not safe credentials.

tee .env >/dev/null <<'EOF'
MARIADB_ROOT_PASSWORD=YOUR_STRONG_ROOT_PASSWORD
MARIADB_PASSWORD=YOUR_STRONG_APP_PASSWORD
EOF
chmod 600 .env

Now create compose.yaml:

services:
  web:
    image: httpd:2.4-alpine
    ports:
      - "127.0.0.1:8081:80"
    volumes:
      - ./public:/usr/local/apache2/htdocs:ro
    depends_on:
      database:
        condition: service_healthy

  database:
    image: mariadb:11
    environment:
      MARIADB_DATABASE: compose_demo
      MARIADB_USER: compose_app
      MARIADB_PASSWORD: ${MARIADB_PASSWORD:?Set MARIADB_PASSWORD in .env}
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:?Set MARIADB_ROOT_PASSWORD in .env}
    volumes:
      - mariadb_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 12

volumes:
  mariadb_data:

The read-only bind mount prevents Apache from changing the page, and the named mariadb_data volume survives container replacement. MariaDB has no ports entry, so it is reachable only from the Compose network; Apache’s 127.0.0.1 binding likewise prevents direct public access. Docker’s Compose file reference explains the service, volume, and dependency keys you can add to a real application.

Step 6: Validate and start the stack

Render the Compose configuration before pulling images so a YAML error or missing password is caught early.

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

config --quiet should produce no output and return status 0. The first pull may take a few minutes. Both services should then be running, with the database marked healthy and Apache bound to loopback:

NAME                         IMAGE             SERVICE    STATUS                   PORTS
compose-jammy-web-1          httpd:2.4-alpine  web        Up                       127.0.0.1:8081->80/tcp
compose-jammy-database-1     mariadb:11        database   Up (healthy)

Container names can differ if you use another directory. Select services by the SERVICE column, such as web or database, in later commands.

Verify Docker Compose on Ubuntu 22.04

Test the page and make a database query from inside the database container. Together, these checks prove that Compose can start both services and that the persistent database accepts connections.

curl --fail --silent --show-error http://127.0.0.1:8081
docker compose exec database sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" -e "SELECT 1 AS compose_ok" "$MARIADB_DATABASE"'
docker compose logs --tail 20 web database

The first command should print the page text, and the query should return one:

Docker Compose on Ubuntu 22.04 is running.
compose_ok
1

Because port 8081 is bound to 127.0.0.1, a browser on your workstation cannot reach it directly. For private browser testing, create an SSH tunnel from your workstation with ssh -L 8081:127.0.0.1:8081 USER@SERVER_IP, then open http://127.0.0.1:8081 locally. If you later publish an application, put it behind TLS and apply a deliberate firewall policy; Docker warns that published container ports can bypass ordinary UFW rules. VPSLake’s UFW firewall generator can help draft a host policy.

Troubleshooting

docker: 'compose' is not a docker command

The Compose CLI plugin is missing, or your shell is using a different Docker installation than the one configured above. Refresh the Jammy package index and install the plugin:

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

Use docker compose with a space. The hyphenated docker-compose command belongs to the legacy standalone installation and is not required for Compose v2.

permission denied while trying to connect to the Docker daemon socket

The current SSH session has not loaded the new docker group, or the account was not added to it. Reconnect, run id -nG, and retry; sudo docker compose ... is a temporary workaround. Remember that docker group membership grants root-equivalent control.

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

APT is not reading Docker’s Jammy repository, commonly because the source file has the wrong suite or the previous update failed. Inspect the source and refresh the package index:

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

The file should contain Suites: jammy, and apt update must complete without an error. If docker.io is already installed, do not blindly remove it on a server with existing workloads; plan the package migration first.

dependency failed to start: container compose-jammy-database-1 is unhealthy

Read the database logs and check that .env contains both password variables with no spaces around the equals signs:

docker compose logs database
docker compose config

MariaDB stores its initial credentials in mariadb_data. Correcting .env does not change a password in an already initialized volume. For a disposable test only, docker compose down -v deletes that volume before a clean docker compose up -d; never use -v when the database contains data you need.

Hardening

  • Keep .env at mode 600, exclude it from source control, and move production secrets to a managed secret store.
  • Keep application ports on loopback until authentication, TLS, and the firewall policy are ready. Do not expose MariaDB to the public internet.
  • Back up mariadb_data and test restoring it before treating the stack as production data.
  • Pin production images to reviewed tags or digests, then update deliberately with docker compose pull followed by docker compose up -d.

FAQ

Is Docker Compose included when I install Docker on Ubuntu 22.04?

It is available as the docker-compose-plugin package when you install Docker Engine from Docker’s official APT repository. Verify the installation with docker compose version; the old docker-compose executable is a separate legacy tool.

Why does this guide use compose.yaml instead of docker-compose.yml?

Compose v2 recognizes both names, but compose.yaml is the current default filename and makes the newer workflow clear. If an existing project uses docker-compose.yml, you can keep it or select another file with docker compose -f FILE up -d.

Does docker compose down delete the MariaDB data?

Normal down removes the containers and Compose network but retains the named mariadb_data volume. Adding -v also deletes that volume, so reserve it for resetting disposable test data.

How do I expose the web service safely?

Keep the sample bound to loopback and place a TLS reverse proxy in front of it, usually on ports 80 and 443. Allow only the required public ports in UFW, account for Docker’s packet-filtering behavior, and never publish MariaDB just to make the application work.

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

How to Choose a Dedicated Server: 12 Specs That Matter

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 ✨