🖥️ VPS · October 31, 2026 · 12 min read

Docker Compose on VPS Ubuntu:
Multi-container App Guide

A step-by-step walkthrough from installing Docker Engine to running a full stack with multiple services — WordPress + MySQL, or Node.js + PostgreSQL + Redis

Docker Compose on Ubuntu VPS — Multi-container App

Table of Contents

  1. What is Docker Compose and Why Use It?
  2. Install Docker Engine and Docker Compose on Ubuntu
  3. docker-compose.yml File Structure
  4. Example: WordPress + MySQL + phpMyAdmin
  5. Example: Node.js + PostgreSQL + Redis
  6. Essential Docker Compose Commands
  7. Networks and Volumes
  8. Managing Secrets with .env Files
  9. Production-ready Tips
  10. Troubleshooting Common Issues
  11. Frequently Asked Questions

What is Docker Compose and Why Use It?

Modern applications rarely run in a single container. A typical e-commerce system needs a web server, database, cache, queue worker, and reverse proxy all working together. Docker Compose lets you define and manage all these multi-container applications through a single YAML file.

AspectDocker (docker run)Docker Compose
ContainersOne at a timeMultiple services at once
NetworkingManual setupAutomatic per-project network
VolumesSpecified as flagsDeclared in YAML
DependenciesManual managementdepends_on + healthcheck
Restart policyPer-container flagrestart: key in YAML

Docker Compose is ideal for development environments and small-to-medium production deployments on a single VPS. For multi-server scaling, consider Docker Swarm or Kubernetes.

Install Docker Engine and Docker Compose on Ubuntu

Recommended: Ubuntu 22.04 LTS or 24.04 LTS for production.

Step 1 — Update packages and install prerequisites

sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release

Step 2 — Add Docker's GPG key and repository

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Step 3 — Install Docker Engine and Compose Plugin

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

Step 4 — Run Docker without sudo

sudo usermod -aG docker $USER
newgrp docker   # or log out and back in

Verify Installation

docker --version          # Docker version 26.x.x
docker compose version    # Docker Compose version v2.x.x
💡 Note Docker Compose v2 uses docker compose (no hyphen) instead of the older docker-compose command. This guide uses v2 syntax throughout.

docker-compose.yml File Structure

services:            # list of services/containers
  web:               # service name (also its hostname on the network)
    image: nginx:alpine
    ports:
      - "80:80"      # host_port:container_port
    volumes:
      - ./html:/usr/share/nginx/html:ro
    depends_on:
      - app
    restart: unless-stopped

  app:
    build: .         # build from Dockerfile in current directory
    environment:
      - NODE_ENV=production
    env_file:
      - .env
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    restart: unless-stopped

volumes:             # named volumes (persistent)
  pgdata:

Example: WordPress + MySQL + phpMyAdmin

mkdir ~/wordpress-stack && cd ~/wordpress-stack
# docker-compose.yml
services:
  wordpress:
    image: wordpress:6.7-php8.2-apache
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - wp_data:/var/www/html
    depends_on:
      mysql:
        condition: service_healthy
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  phpmyadmin:
    image: phpmyadmin:5-apache
    ports:
      - "8081:80"
    environment:
      PMA_HOST: mysql
    depends_on:
      - mysql
    restart: unless-stopped

volumes:
  wp_data:
  mysql_data:
# .env (never commit this to Git)
MYSQL_ROOT_PASSWORD=StrongRootPass2026!
MYSQL_DATABASE=wordpress_db
MYSQL_USER=wpuser
MYSQL_PASSWORD=StrongUserPass2026!
docker compose up -d
docker compose ps

Example: Node.js + PostgreSQL + Redis

# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --save 20 1 --loglevel warning
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  pg_data:
  redis_data:

Essential Docker Compose Commands

CommandDescription
docker compose up -dStart all services in detached mode
docker compose downStop and remove containers (volumes preserved)
docker compose down -vStop and remove containers + volumes
docker compose psShow status of all services
docker compose logs -fStream logs from all services
docker compose logs -f webStream logs from "web" service only
docker compose exec app shOpen shell in "app" container
docker compose restartRestart all services
docker compose pullPull latest images
docker compose buildRebuild images from Dockerfile
docker compose configValidate and view the merged compose config

Networks and Volumes

Networking

Docker Compose automatically creates a default network for your project. All services in the same file can reach each other using the service name as hostname:

DATABASE_URL=postgresql://user:pass@postgres:5432/mydb
#                                   ^^^^^^^ service name
⚠️ Security Tip Avoid exposing database ports (3306, 5432) directly to the host unless necessary. Use firewall rules or keep them on internal Docker networks only.

Volumes

Managing Secrets with .env Files

Docker Compose automatically loads a .env file from the same directory. Use ${VARIABLE_NAME} in your compose file to reference variables.

# .gitignore — never commit .env!
.env
*.env
!.env.example
💡 Best Practice Create a .env.example file with variable names but no real values so teammates know what to configure without exposing secrets.

Production-ready Tips

1. Restart Policy

restart: unless-stopped  # always restart, except manual docker compose stop

2. Resource Limits

deploy:
  resources:
    limits:
      cpus: "0.5"
      memory: 512M
    reservations:
      memory: 256M

3. Healthchecks

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

4. Reverse Proxy with Nginx

Run Nginx as a separate service (or outside Docker Compose) acting as a reverse proxy and handling SSL termination via Let's Encrypt/Certbot.

5. Volume Backups

docker run --rm \
  -v wordpress-stack_mysql_data:/data \
  -v $(pwd)/backup:/backup \
  alpine tar czf /backup/mysql-$(date +%Y%m%d).tar.gz /data

Troubleshooting Common Issues

ProblemCauseSolution
Container exits immediatelyEntrypoint error or healthcheck failCheck with docker compose logs <service>
Port already in usePort conflicts with another processRun sudo ss -tlnp | grep PORT to identify
Volume data lostRan down -v unintentionallyVerify backups before running down
Services can't communicateServices on different networksCheck with docker compose config
Permission denied on volumeUID/GID mismatch between host and containerAdd user: "UID:GID" to the service

Frequently Asked Questions

What is the difference between Docker and Docker Compose?
Docker manages single containers via CLI commands. Docker Compose manages multiple containers simultaneously using a YAML file, defining networks, volumes, and service dependencies in one place.
How much RAM does my VPS need for Docker Compose?
Minimum 2GB RAM for 2-3 services (Web + Database + Cache). For 5+ services, 4GB or more is recommended. AsiaGB VPS plans range from 1GB to 8GB — pick based on your stack size.
Where should I store my docker-compose.yml file?
Create a separate directory for each project (e.g., ~/myapp/) and keep docker-compose.yml with a .env file there. Commit the compose file to Git but never commit the .env file.
How do I make Docker Compose start automatically after a VPS reboot?
Add restart: unless-stopped to every service in docker-compose.yml. Docker Engine will automatically restart containers when the system boots up.
How do I update images in Docker Compose?
Run docker compose pull to fetch latest images, then docker compose up -d to recreate containers with the new images. Services without changes won't be restarted.
Does Docker Compose work on all Linux VPS distributions?
Yes — Ubuntu, Debian, CentOS, AlmaLinux, and more. Ubuntu LTS is recommended for production due to its large community and regular security updates.

Summary

Docker Compose gives you an organized way to manage multi-container applications on a single VPS. The key steps: install Docker Engine + Compose Plugin, write a docker-compose.yml defining your services, store secrets in .env files, and add restart policies for automatic recovery after reboots.

For Docker Compose in production, we recommend a VPS with at least 2GB RAM and SSD storage. AsiaGB offers Thailand and Singapore VPS with SSD, 99% uptime guarantee, and competitive pricing.