Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Docker Compose Ubuntu
GUIDEInstall Guides

How to Install Docker Compose on Ubuntu 24.04

18 min read

How to Install Docker Compose on Ubuntu 24.04 — Multi-Container Guide

Quick Summary

Docker Compose is the tool for defining and running multi-container Docker
applications from a single YAML file. This guide covers installing Docker
Compose v2 on Ubuntu 24.04, writing your first docker-compose.yml,
production patterns, and troubleshooting. Estimated time: 10 minutes.
>
Skip the setup? Deploy a pre-configured Docker VPS
with Docker and Compose v2 preinstalled in under 2 minutes.

Table of Contents

  • What is Docker Compose?
  • Docker Compose v1 vs v2
  • Prerequisites
  • Method 1: Install via Docker Plugin (Recommended)
  • Method 2: Install the Standalone Binary
  • Method 3: Install via apt (docker-compose-plugin)
  • Verify the Installation
  • Your First Compose File: WordPress + MySQL
  • Essential Compose Commands
  • Environment Variables and .env Files
  • Networks: Internal vs External
  • Volumes: Named vs Bind Mounts
  • depends_on and Healthchecks
  • Scaling Services
  • Production Patterns
  • Secrets Management
  • Overriding with docker-compose.override.yml
  • Running Multiple Projects on the Same Host
  • Updating Containers
  • Backup Strategy for Volumes
  • Troubleshooting
  • FAQ
  • Next Steps

What is Docker Compose?

Docker Compose is a tool for defining and running multi-container applications. Instead of typing long docker run commands with flags for ports, volumes, networks, and environment variables, you describe the entire stack in a single YAML file called docker-compose.yml. One command — docker compose up — then starts every service with the right configuration, in the right order, on the right network.

Compose was originally a separate Python tool (docker-compose), but in 2021 Docker rewrote it as a Go plugin integrated into the Docker CLI. That plugin is Compose v2, which is the only version supported today. If you are reading tutorials that use the hyphenated docker-compose command, you are reading v1 material that no longer matches modern Docker installations.

Compose shines for development environments, small-to-medium production deployments, CI pipelines, and single-host stacks like a WordPress site, a Mautic marketing server, or a self-hosted GitLab runner. For multi-host orchestration you eventually graduate to Docker Swarm or Kubernetes, but Compose remains the fastest way to get from a README to a running stack.

Docker Compose v1 vs v2

AspectCompose v1 (legacy)Compose v2 (current)
LanguagePythonGo
Commanddocker-compose (with hyphen)docker compose (space, a plugin subcommand)
PackagingStandalone pip installDocker CLI plugin
StatusEnd of life, no updates since 2023Actively maintained by Docker, Inc.
Config filedocker-compose.ymlcompose.yaml or docker-compose.yml
SpeedSlower, single-threadedFaster, parallel operations
The key takeaway: use v2. Every example in this guide uses docker compose with a space. If a project still ships a v1 docker-compose.yml, v2 reads it without changes — the file format is backward compatible.

Prerequisites

Before you begin, you need:

  • A VPS with at least 1 vCPU, 2 GB RAM, 20 GB storage
> Recommended: Our CloudCore Starter plan > at EUR 7.99/mo is enough for a small WordPress + MySQL stack. For multiple > stacks or heavier services, step up to CloudCore Professional.

  • Ubuntu 24.04 LTS (fresh install recommended)
  • Docker Engine already installed — if not, follow our
How to Install Docker on Ubuntu 24.04 guide first. Compose v2 is a plugin that extends the Docker CLI, so Docker must exist first.
  • SSH access with a non-root user in the docker group
  • A domain name pointed to your server IP (optional, needed for HTTPS)
Check Docker is installed and your user can talk to it without sudo:

bash
docker --version
docker run hello-world

If the second command prints a "Hello from Docker!" banner, you are ready.

Method 1: Install via Docker Plugin (Recommended)

If you installed Docker using the official get.docker.com script or Docker's official apt repository, Compose v2 is already installed as a plugin. You do not need to do anything else — skip to Verify the Installation.

If you are not sure, run:

bash
docker compose version

If you see Docker Compose version v2.x.x, you are done. If you see a "command not found" or "compose is not a docker command" error, continue to Method 3 below to install the plugin via apt.

Method 2: Install the Standalone Binary

Use this method when you need a specific Compose version that is newer than what your distro ships, when you are on a minimal system without apt, or when you are building a portable CI image.

bash
# Get the latest version tag from GitHub
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep tag_name | cut -d '"' -f 4)

Create the plugin directory for the current user

mkdir -p ~/.docker/cli-plugins

Download the binary matching your architecture

curl -SL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \ -o ~/.docker/cli-plugins/docker-compose

Make it executable

chmod +x ~/.docker/cli-plugins/docker-compose

To install system-wide so every user gets Compose, write to /usr/local/lib/docker/cli-plugins/ instead of ~/.docker/cli-plugins/, and prefix the mkdir, curl, and chmod commands with sudo.

Verify:

bash
docker compose version

Method 3: Install via apt (docker-compose-plugin)

If you installed Docker from Docker's official apt repository, you can install Compose as an apt package. This is the cleanest option because apt will keep it updated alongside Docker Engine:

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

Verify:

bash
docker compose version

Expected output:

text
Docker Compose version v2.29.7

If apt cannot find the package, the Docker official apt repo is not configured. Follow the Docker install guide referenced in the prerequisites to add it.

Verify the Installation

Regardless of which method you used, verify Compose is working end to end:

bash
docker compose version
docker compose --help | head -20

The first command prints the version. The second lists every available subcommand — up, down, logs, ps, exec, build, pull, restart, and more. If both work, Compose is installed correctly.

Your First Compose File: WordPress + MySQL

Let's prove Compose works with a real example. Create a new project directory and a docker-compose.yml inside it:

bash
mkdir -p ~/wordpress-stack && cd ~/wordpress-stack
nano docker-compose.yml

Paste this complete, working stack:

yaml
services:
  db:
    image: mysql:8.0
    container_name: wp_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: change_me_root
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: change_me_user
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

wordpress: image: wordpress:6-php8.3-apache container_name: wp_app restart: unless-stopped depends_on: db: condition: service_healthy environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_NAME: wordpress WORDPRESS_DB_USER: wp_user WORDPRESS_DB_PASSWORD: change_me_user volumes: - wp_data:/var/www/html networks: - backend - frontend ports: - "8080:80"

volumes: db_data: wp_data:

networks: backend: internal: true frontend:

Start the stack:

bash
docker compose up -d

The -d flag runs containers in the background (detached). Compose pulls both images, creates named volumes, builds the two networks, waits for MySQL to pass its healthcheck, and then starts WordPress. Open http://YOUR_SERVER_IP:8080 in your browser and walk through the WordPress setup screen.

Stop it when you are done experimenting:

bash
docker compose down

Add -v to also delete the volumes: docker compose down -v. Without -v, your database and uploaded files survive so you can bring the stack back up with the same data.

Essential Compose Commands

Every Compose command runs from the directory that contains your docker-compose.yml. Here are the ones you will use daily:

CommandWhat it does
docker compose up -dCreate and start all services in the background
docker compose downStop and remove containers, networks (volumes survive)
docker compose down -vSame as above but also delete named volumes
docker compose psList containers in this project and their status
docker compose logs -fStream logs from all services (-f follows)
docker compose logs -f wordpressStream logs from one service
docker compose exec wordpress bashOpen a shell inside a running container
docker compose restart wordpressRestart a single service
docker compose pullPull newer versions of every image
docker compose buildBuild images for services with a build: directive
docker compose configValidate and print the resolved configuration
docker compose topShow running processes inside each container

Environment Variables and .env Files

Hardcoding passwords in docker-compose.yml is a bad habit. Compose reads an .env file in the same directory and expands ${VAR} references in the YAML:

bash
nano ~/wordpress-stack/.env
env
MYSQL_ROOT_PASSWORD=supersecret_root
MYSQL_USER=wp_user
MYSQL_PASSWORD=supersecret_user
WP_PORT=8080

Then in docker-compose.yml:

yaml
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
  wordpress:
    ports:
      - "${WP_PORT}:80"

Add .env to your .gitignore so secrets never reach a public repo. Commit a .env.example with dummy values so teammates know which variables to set.

You can also load variables into a container in bulk with env_file:

yaml
services:
  app:
    image: myapp:latest
    env_file:
      - ./config/app.env

Networks: Internal vs External

Compose automatically creates one network per project named <projectname>_default, and every service joins it. Services on the same network reach each other by service name as DNS. In the WordPress example, WordPress talks to db:3306 — "db" resolves to MySQL's container IP.

Internal networks have no route to the outside world. Set internal: true on a network to isolate a database from the internet even if the host's firewall is misconfigured:

yaml
networks:
  backend:
    internal: true

External networks let you share a network between projects. Create the network once with docker network create proxy_net, then reference it:

yaml
networks:
  proxy:
    external: true
    name: proxy_net

This pattern is how reverse proxies like Traefik or Nginx Proxy Manager talk to application containers in other Compose projects on the same host.

Volumes: Named vs Bind Mounts

Compose supports two ways to persist data.

Named volumes are managed by Docker and live under /var/lib/docker/volumes/. Use them for databases and anything you do not need to edit from the host:

yaml
services:
  db:
    volumes:
      - db_data:/var/lib/mysql

volumes: db_data:

Bind mounts map a specific host path into the container. Use them for configuration files or code you edit on the host:

yaml
services:
  nginx:
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./html:/usr/share/nginx/html

The :ro suffix makes the mount read-only — good defense for config files a container should never modify.

depends_on and Healthchecks

depends_on controls startup order. On its own it only waits for the dependency container to start, not for the service inside to be ready. A MySQL container is "started" in seconds but takes longer to accept connections. Combine depends_on with a healthcheck for reliable ordering:

yaml
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 30s

api: image: myapi:latest depends_on: db: condition: service_healthy

Now api only starts once Postgres answers pg_isready. The start_period grace window suppresses failures during the initial boot.

Common healthcheck patterns:

ServiceTest
MySQL/MariaDB["CMD", "mysqladmin", "ping", "-h", "localhost"]
Postgres["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
Redis["CMD", "redis-cli", "ping"]
HTTP API["CMD", "curl", "-f", "http://localhost:8080/health"]

Scaling Services

Compose can run multiple copies of a stateless service on the same host:

bash
docker compose up -d --scale worker=4

This starts four replicas of the worker service. The service must not bind a fixed host port — Docker cannot map port 8080 on the host to four containers. Put a reverse proxy in front (Traefik, Nginx, HAProxy) and let it load-balance by service name.

For multi-host scaling, Compose itself does not orchestrate across machines — that is where Docker Swarm or Kubernetes come in.

Production Patterns

Restart Policies

restart: unless-stopped is the safest default for production. It restarts a crashed container automatically but respects manual docker compose stop:

yaml
services:
  app:
    restart: unless-stopped

Other options: no (never), always (even after manual stop), on-failure (only on non-zero exit, optionally with a retry count).

Resource Limits

Cap CPU and memory so a runaway container cannot starve the rest of the host:

yaml
services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          memory: 256M

Compose respects deploy.resources when running on a plain host — you do not need Swarm mode for this to work.

Logging Config

By default Docker's json-file driver will grow forever until the disk is full. Cap it per service:

yaml
services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Production-Ready Template

yaml
services:
  app:
    image: myapp:1.4.2
    container_name: myapp
    restart: unless-stopped
    env_file:
      - ./.env
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - app_data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes: app_data:

networks: backend:

Note 127.0.0.1:8080:8080 — binding to localhost on the host means only a reverse proxy on the same host can reach the app. The port is not exposed publicly, which is exactly what you want behind Traefik or Nginx.

Pin image tags to specific versions (myapp:1.4.2) in production, not latest. You want predictable updates.

Secrets Management

For truly sensitive values — TLS private keys, API tokens — prefer Docker secrets over environment variables. Compose supports file-based secrets:

yaml
services:
  app:
    image: myapp:latest
    secrets:
      - db_password
      - api_token

secrets: db_password: file: ./secrets/db_password.txt api_token: file: ./secrets/api_token.txt

Inside the container each secret is mounted as a read-only file under /run/secrets/<name>. Set filesystem permissions on the host so only root can read the files on disk. For stronger guarantees use Swarm mode or an external secret store like HashiCorp Vault.

Overriding with docker-compose.override.yml

If a file named docker-compose.override.yml sits next to docker-compose.yml, Compose merges it automatically. This is the idiomatic way to split dev-only settings (code bind mounts, debug ports) from the shared base:

yaml
# docker-compose.override.yml — used on dev laptops, not in prod
services:
  app:
    volumes:
      - ./src:/app/src
    environment:
      DEBUG: "true"
    ports:
      - "9229:9229"  # Node inspector

On the production server, delete or rename the override file so only the base runs. You can also name it explicitly:

bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

The later file wins on conflicts. Use this to layer environment-specific tweaks without duplicating the full stack.

Running Multiple Projects on the Same Host

Each Compose project is identified by the directory name (or by -p / COMPOSE_PROJECT_NAME). You can run WordPress, Mautic, and a staging clone of WordPress on the same host as long as you avoid port conflicts.

Two tricks:

  • Bind to different host ports. Give each project a unique external port.
  • WordPress on 8080, Mautic on 8081, WordPress-staging on 8082.
  • Use a reverse proxy and drop public port mappings. Run one Traefik or
  • Nginx Proxy Manager container that listens on 80/443 and routes by domain. Application containers do not publish ports at all — they join a shared external network named proxy_net and the proxy reaches them by service name. This scales to dozens of apps.

    Network isolation still holds: each project has its own default network, and databases stay on internal networks. Only the proxy bridges public traffic in.

    Updating Containers

    The standard update flow is two commands:

    bash
    docker compose pull
    docker compose up -d

    pull fetches newer image layers for any tag that has been republished. up -d recreates containers whose image hash changed and leaves unchanged services alone. Volumes survive, so your data stays intact.

    After the update, prune the old image layers to reclaim disk:

    bash
    docker image prune -f

    For major version bumps (MySQL 5.7 to 8.0, Postgres 15 to 16) read the project's upgrade notes first. Database engines sometimes require running a migration tool against the old data directory before the new version will boot.

    Backup Strategy for Volumes

    Compose named volumes are just directories under /var/lib/docker/volumes/, but you should never tar them while the service is running — you risk copying mid-write files. Safer options:

    Database-aware backup (preferred for databases):

    bash
    docker compose exec db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases \
      | gzip > /backup/db_$(date +%F).sql.gz

    Stop-copy-start (simple but causes a brief outage):

    bash
    docker compose stop
    tar czf /backup/volumes_$(date +%F).tar.gz -C /var/lib/docker/volumes .
    docker compose start

    Live volume snapshot via a helper container:

    bash
    docker run --rm \
      --volumes-from wp_db \
      -v /backup:/backup \
      alpine tar czf /backup/wp_db_$(date +%F).tar.gz /var/lib/mysql

    Schedule whichever approach you pick in cron and ship the archives off the host — an S3 bucket, a second VPS, or restic to a backup provider.

    Troubleshooting

    ProblemCauseSolution
    Bind for 0.0.0.0:80 failed: port is already allocatedAnother container or host service owns the portFind it with sudo ss -tlnp \</td><td>grep :80<code>, change the host port in </code>ports:, or stop the other service
    permission denied on a bind-mounted volumeContainer user UID does not match host file ownerRun sudo chown -R 1000:1000 ./data (or whatever UID the image uses)
    could not resolve host: db inside a containerServices not on the same networkConfirm both list the same entry under networks:, run docker compose config to inspect
    compose is not a docker commandv2 plugin not installedInstall with Method 3: sudo apt install docker-compose-plugin
    container is unhealthy blocks startupHealthcheck fails repeatedlyRun docker compose logs db and docker inspect --format='{{json .State.Health}}' wp_db \</td><td>jq to see why
    Changes to docker-compose.yml not appliedup only recreates when image changesForce recreation: docker compose up -d --force-recreate
    Old images eating diskLayers left behind after updatesdocker image prune -a -f (careful — removes unused images too)
    yaml: line N: mapping values are not allowed hereYAML indentation errorTwo-space indent everywhere, no tabs; validate with docker compose config

    FAQ

    Q: What is the difference between Docker Compose, Docker Swarm, and Kubernetes?

    A: Compose runs multi-container apps on one host — simple, fast to set up, perfect for small-to-medium production workloads. Swarm is Docker's built-in multi-host orchestrator: it takes a Compose file and runs it across a cluster with rolling updates and a built-in load balancer. Kubernetes is the industry-standard orchestrator for large, complex deployments — far more powerful and far more complex to operate. For a single VPS running a handful of apps, Compose is the right tool. Scale up to Swarm or K8s only when you outgrow one host.

    Q: Should I still use docker-compose with a hyphen?

    A: No. Compose v1 reached end of life and stopped getting updates. Every modern tutorial, image README, and CI template uses docker compose with a space. If you have muscle memory for the hyphen, set a shell alias: alias docker-compose='docker compose'.

    Q: Can I use Compose for development and Dockerfiles for building?

    A: Yes — that is the standard workflow. Point build: at a directory with a Dockerfile and Compose will build the image before starting the service:

    yaml
    services:
      app:
        build: ./app
        ports:
          - "3000:3000"

    Run docker compose build to rebuild, or docker compose up --build to rebuild and start in one shot.

    Q: How do I back up a Compose stack?

    A: Back up the volumes, not the containers. Containers are disposable — you can always recreate them from the docker-compose.yml. See the Backup Strategy for Volumes section above for database-aware and file-level options.

    Q: Is there a GUI for managing Compose stacks?

    A: Yes — Portainer is the most popular. It gives you a web UI for every Compose stack on the host, including editing YAML in the browser, viewing logs, and restarting services. Coolify goes a step further with a full Heroku-like PaaS experience on top of Compose.

    Next Steps

    • How to Install Portainer on Ubuntu — web UI for every Compose stack on your host
    • How to Install Coolify on Ubuntu — self-hosted Heroku built on Docker Compose
    • How to Install Traefik Reverse Proxy — automatic HTTPS and domain routing for all your stacks
    • How to Set Up Automated Backups — never lose a volume
    • How to Install Docker on Ubuntu 24.04 — the prerequisite guide

    ### Skip the Manual Install
    >
    We offer Docker with Compose v2 preinstalled on every VPS plan.
    Your server comes ready to docker compose up in under 2 minutes.
    >
    Deploy a Docker VPS Now | Starting from EUR 7.99/mo | 172+ 1-click apps | 9 global locations

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket