How to Install Docker Compose on VPS (2025) — Complete Guide
What Is Docker Compose and Why You Need It
Docker Compose is a tool for defining and running multi-container Docker applications. Instead of typing long docker run commands with dozens of flags, you describe your entire application stack in a single YAML file called docker-compose.yml. For Docker fundamentals, see Docker on Ubuntu VPS. One command — docker compose up -d — creates all the containers, networks, and volumes your application needs. For hosting recommendations, see VPS for Docker and VPS for Self-Hosting.
This is essential for modern application deployments. Most real-world applications consist of multiple services: a web server, an application backend, a database, a cache layer, and perhaps a message queue. Managing these as individual containers with separate docker run commands is error-prone and hard to reproduce. Docker Compose solves this by treating the entire stack as a single unit. For automation use cases, see Setup n8n on VPS and Deploy FastAPI on VPS.
Docker Compose V2 (the current version) is installed as a Docker CLI plugin. You invoke it as docker compose (with a space) rather than the older standalone docker-compose binary (with a hyphen). The V1 standalone binary is deprecated and no longer receives updates. All modern Docker installations include the Compose V2 plugin by default.
Prerequisites
| Requirement | Details |
|---|---|
| Operating System | Ubuntu 22.04 LTS or 24.04 LTS |
| Root Access | SSH access with root privileges or a sudo user |
| RAM | Minimum 1GB (2GB+ recommended for multi-container stacks) |
| Disk Space | Minimum 10GB free for images and container data |
Step 1: Install Docker Engine
Docker Compose requires Docker Engine as a prerequisite. Install Docker CE from the official Docker repository to get the latest stable version.
Connect to your VPS and update the system.
ssh root@your-vps-ip
apt update && apt upgrade -y
Install the prerequisites for adding a third-party repository.
apt install -y ca-certificates curl gnupg lsb-release apt-transport-https software-properties-common
Add Docker's official GPG key.
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
Add the Docker APT repository.
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
Step 2: Install Docker Compose Plugin
Install Docker Engine along with the Compose V2 plugin. The Compose plugin is bundled as docker-compose-plugin and integrates directly into the Docker CLI.
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enable and start Docker.
systemctl enable docker
systemctl start docker
If you want to run Docker commands without sudo, add your user to the docker group.
usermod -aG docker $USER
su - $USER
Step 3: Verify Installation
Check that both Docker Engine and Docker Compose are installed correctly.
docker --version
docker compose version
You should see output similar to:
Docker version 27.x.x, build xxxxxxx
Docker Compose version v2.x.x
Run a quick test to confirm Docker is fully operational.
docker run --rm hello-world
If you see the "Hello from Docker!" message, both Docker Engine and the Compose plugin are working correctly.
Step 4: Create Your First docker-compose.yml
Create a project directory and your first Docker Compose file. This example defines a simple web application with Nginx serving static content.
mkdir -p /opt/myapp && cd /opt/myapp
cat > docker-compose.yml << 'EOF'
services:
web:
image: nginx:alpine
container_name: myapp-web
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
restart: unless-stopped
EOF
Create a test HTML file.
mkdir -p html
cat > html/index.html << 'EOF'
My Docker Compose App
Hello from Docker Compose!
This is running on Nginx inside a Docker container.
EOF
Start the application.
docker compose up -d
Visit http://your-vps-ip to see the page. Docker Compose pulled the Nginx Alpine image, created a container, and started it — all from a single YAML file.
Stop and remove the containers when you are done.
docker compose down
Step 5: Multi-Container Application Example
This is where Docker Compose truly shines. The following example defines a complete application stack with Nginx (reverse proxy), a Python Flask application, and a PostgreSQL database.
cat > docker-compose.yml << 'EOF'
version: "3.9"
services:
# Nginx reverse proxy
nginx:
image: nginx:alpine
container_name: app-nginx
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app:
condition: service_healthy
restart: unless-stopped
networks:
- app-network
# Flask application
app:
build: ./app
container_name: app-flask
environment:
- DATABASE_URL=postgresql://appuser:apppass@db:5432/appdb
- FLASK_ENV=production
expose:
- "5000"
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
networks:
- app-network
# PostgreSQL database
db:
image: postgres:16-alpine
container_name: app-postgres
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppass
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432"
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
volumes:
postgres_data:
networks:
app-network:
driver: bridge
EOF
Create the Flask Application
mkdir -p app
cat > app/Dockerfile << 'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
EOF
cat > app/requirements.txt << 'EOF'
flask==3.0.0
gunicorn==21.2.0
psycopg2-binary==2.9.9
EOF
cat > app/app.py << 'EOF'
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def index():
return jsonify({"message": "Hello from Docker Compose!"})
@app.route('/health')
def health():
return jsonify({"status": "healthy"}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
Create the Nginx Configuration
mkdir -p nginx
cat > nginx/default.conf << 'EOF'
upstream flask_app {
server app:5000;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://flask_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
Build and start the entire stack.
cd /opt/myapp
docker compose up -d --build
Docker Compose builds the Flask image, pulls Nginx and PostgreSQL images, creates the network and volume, and starts all three services in dependency order. The depends_on with condition: service_healthy ensures the database is ready before the app starts, and the app is healthy before Nginx begins routing traffic to it.
Step 6: Managing Containers
Docker Compose provides a complete set of commands for managing the lifecycle of your application stack.
Starting and Stopping
# Start all services in detached mode
docker compose up -d
# Start with verbose output (foreground, useful for debugging)
docker compose up
# Stop all services (containers are preserved)
docker compose stop
# Stop and remove all containers, networks
docker compose down
# Stop and remove containers, networks, AND volumes (deletes database data!)
docker compose down -v
# Restart all services
docker compose restart
# Restart a single service
docker compose restart app
Viewing Status and Logs
# View status of all services
docker compose ps
# View logs for all services
docker compose logs -f
# View logs for a specific service
docker compose logs -f app
# View last 100 lines of logs
docker compose logs --tail=100 app
Scaling Services
# Scale a service to multiple instances
docker compose up -d --scale app=3
Note: Scaling with Docker Compose works well for stateless services. Stateful services like databases should not be scaled this way. For production scaling, consider using Docker Swarm or Kubernetes.
Step 7: Environment Variables and .env Files
Environment variables allow you to configure your application without modifying the docker-compose.yml file. Docker Compose supports multiple ways to define and inject environment variables.
Using a .env File
Create a .env file in the same directory as your docker-compose.yml. Docker Compose automatically reads this file.
cat > .env << 'EOF'
POSTGRES_USER=appuser
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_DB=appdb
FLASK_ENV=production
APP_VERSION=1.0.0
EOF
Reference these variables in your docker-compose.yml using the ${VARIABLE_NAME} syntax.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
Variable Substitution with Defaults
You can provide default values that are used when the variable is not set.
environment:
- FLASK_ENV=${FLASK_ENV:-development}
- LOG_LEVEL=${LOG_LEVEL:-info}
- WORKERS=${WORKERS:-2}
Environment-Specific Compose Files
Create separate override files for different environments. Docker Compose merges multiple compose files automatically.
# Production overrides (docker-compose.prod.yml)
services:
app:
environment:
- FLASK_ENV=production
restart: always
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
# Use: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Development overrides (docker-compose.dev.yml)
services:
app:
volumes:
- ./app:/app # Hot-reload source code
environment:
- FLASK_ENV=development
# Use: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
Passing Variables at Runtime
# Set environment variables inline
POSTGRES_PASSWORD=secretpass docker compose up -d
# Or use --env-file to specify a custom .env file
docker compose --env-file .env.production up -d
Security Warning: Never commit your .env file to version control. Add .env to your .gitignore file. Use .env.example (without real values) as a template for other developers.
Step 8: Networks and Volumes
Docker Compose Networks
Every Docker Compose project automatically creates a default bridge network. All services in the compose file can communicate with each other using their service names as hostnames. You can also define custom networks for more complex topologies.
services:
frontend:
image: nginx:alpine
networks:
- front-tier
- back-tier
backend:
image: node:20-alpine
networks:
- back-tier
database:
image: postgres:16-alpine
networks:
- back-tier
networks:
front-tier:
driver: bridge
back-tier:
driver: bridge
internal: true # No outbound internet access
In this example, the frontend can reach both the frontend and backend tiers, but the backend and database are isolated on an internal network with no internet access. This is a security best practice for databases and internal services.
Docker Compose Volumes
Volumes persist data beyond the lifecycle of a container. When you run docker compose down, container data is deleted. Named volumes survive this operation.
services:
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data # Named volume (managed by Docker)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # Bind mount (file)
- ./backups:/backups # Bind mount (directory)
app:
image: node:20-alpine
volumes:
- app_data:/app/data # Named volume
- ./config:/app/config:ro # Read-only bind mount
volumes:
postgres_data:
driver: local
app_data:
driver: local
Manage volumes with Docker Compose commands.
# List volumes in the current project
docker compose volume ls
# Create volumes without starting services
docker compose volume create postgres_data
# Remove unused volumes
docker compose volume prune
Step 9: Production Tips
Restart Policies
Choose the appropriate restart policy for each service based on its role in your stack.
services:
web:
restart: unless-stopped # Recommended for most services
# Restarts unless explicitly stopped with docker compose stop
db:
restart: always # Database should always restart
# Restarts even after the Docker daemon restarts
migration:
restart: "no" # One-time tasks should not restart
# Run once and stay stopped if they exit
Health Checks
Health checks allow Docker to monitor the actual health of your services, not just whether the process is running. This is critical for dependency management — you want your app to wait for the database to be truly ready, not just have its container started.
services:
app:
image: myapp:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s # Check every 30 seconds
timeout: 5s # Timeout after 5 seconds
retries: 3 # Fail after 3 consecutive failures
start_period: 15s # Grace period before first check
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
Resource Limits
Prevent any single container from consuming all available resources on your VPS.
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 512M # Maximum memory usage
cpus: '1.0' # Maximum CPU (1 core)
reservations:
memory: 128M # Guaranteed minimum memory
cpus: '0.25' # Guaranteed minimum CPU
Logging Configuration
By default, Docker stores logs indefinitely, which can fill your VPS disk. Configure log rotation to prevent this.
services:
app:
image: myapp:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
This limits each log file to 10MB and keeps a maximum of 3 rotated log files per container, using at most 30MB per service.
Read-Only Root Filesystem
For improved security, run containers with a read-only root filesystem and only allow writes to specific directories.
services:
web:
image: nginx:alpine
read_only: true
tmpfs:
- /tmp
- /var/run
- /var/cache/nginx
Step 10: Common Commands Reference
Here is a quick reference for the Docker Compose commands you will use most frequently on your VPS.
| Command | Description |
|---|---|
docker compose up -d | Build, create, and start all services in detached mode |
docker compose down | Stop and remove containers, networks |
docker compose down -v | Stop and remove containers, networks, and volumes |
docker compose ps | List running services |
docker compose logs -f | Follow logs for all services |
docker compose logs -f app | Follow logs for a specific service |
docker compose restart app | Restart a specific service |
docker compose pull | Pull the latest images for all services |
docker compose up -d --build | Rebuild images and restart services |
docker compose exec app bash | Execute a command in a running service |
docker compose run app python manage.py migrate | Run a one-off command in a new container |
docker compose config | Validate and view the merged configuration |
docker compose top | Display running processes in each service |
docker compose images | List images used by services |
docker compose volume ls | List volumes in the project |
Frequently Asked Questions
What is the difference between Docker Compose V1 and V2?
Docker Compose V1 was a standalone Python binary invoked as docker-compose (with a hyphen). V2 is a Go-based plugin integrated into the Docker CLI, invoked as docker compose (with a space). V1 is deprecated. V2 is faster, better maintained, and included with all modern Docker installations.
Can I run Docker Compose without Docker Engine?
No. Docker Compose is a tool that manages Docker containers. It requires Docker Engine (the Docker daemon) to be installed and running. Compose sends instructions to the Docker Engine API to create and manage containers.
How do I update Docker Compose?
Docker Compose V2 is updated along with Docker Engine. Run apt update && apt upgrade -y to update both. If you installed Compose separately, update it by downloading the latest binary from the Docker GitHub releases page.
Can I use Docker Compose in production?
Yes, with proper configuration. Use health checks, restart policies, resource limits, log rotation, and read-only filesystems. For large-scale production, consider Docker Swarm or Kubernetes, but Docker Compose is perfectly suitable for single-server production deployments.
How do I back up Docker Compose volumes?
Use Docker to create a tar archive of the volume contents: docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .. Automate this with a cron job for regular backups.
How do I share environment variables between services?
Define the variables at the service level with environment:, or use an env_file: directive to load variables from a file. Variables defined at the top level of the compose file can be referenced by all services using ${VAR_NAME}.
Can Docker Compose manage containers across multiple servers?
Standard Docker Compose manages containers on a single host. For multi-server deployments, use Docker Swarm mode (which supports the Compose file format) or Kubernetes. There are also third-party tools like Podman Compose that can work across hosts.
How do I debug a failing Docker Compose service?
Check the logs with docker compose logs -f service_name. Inspect the container with docker compose exec service_name sh. Verify the health check endpoint is reachable. Use docker compose config to validate the merged configuration. Check resource limits with docker stats.