Docker Compose on a VPS is the sweet spot between shared hosting and full Kubernetes. You get container isolation and reproducible deployments without the complexity overhead. Here is how to run it in production.
Initial VPS Setup
Start with a clean Ubuntu 22.04 or Debian 12 VPS. At least 2GB RAM for small applications, 4GB+ for multiple services.
Security First
# Update system
apt update && apt upgrade -y
# Create a non-root user
adduser deploy
usermod -aG sudo deploy
# Configure SSH
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
# Firewall
ufw allow ssh
ufw allow http
ufw allow https
ufw enable
Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
usermod -aG docker deploy
Production Docker Compose
Directory Structure
/opt/myapp/
├── docker-compose.yml
├── .env
├── nginx/
│ └── conf.d/
├── data/
│ ├── postgres/
│ └── redis/
└── backups/
Example Compose File
version: '3.8'
services:
app:
image: myapp:latest
restart: unless-stopped
env_file: .env
depends_on:
- db
- redis
networks:
- internal
- web
db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
networks:
- internal
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- ./data/redis:/data
networks:
- internal
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- app
networks:
- web
networks:
internal:
driver: bridge
web:
driver: bridge
Reverse Proxy with SSL
Use Nginx as a reverse proxy with Let's Encrypt SSL:
# Install Certbot
apt install certbot python3-certbot-nginx
# Get certificate
certbot certonly --standalone -d yourdomain.com
Configure Nginx to proxy traffic to your application containers.
Deployment Workflow
Manual Deployment
cd /opt/myapp
docker compose pull
docker compose up -d --build
docker compose logs -f app
Automated Deployment with Git
Set up a simple deployment script:
#!/bin/bash
cd /opt/myapp
git pull origin main
docker compose build --no-cache app
docker compose up -d app
docker image prune -f
Monitoring
Container Health
# Check all container status
docker compose ps
# View resource usage
docker stats --no-stream
Log Management
Configure log rotation to prevent disk fill:
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Backups
Back up your data volumes regularly:
#!/bin/bash
BACKUP_DIR="/opt/myapp/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# Stop database for consistent backup
docker compose stop db
# Backup data directory
tar czf "${BACKUP_DIR}/data_${DATE}.tar.gz" ./data/
# Restart database
docker compose start db
# Remove backups older than 7 days
find ${BACKUP_DIR} -name "data_*.tar.gz" -mtime +7 -delete
Performance Tips
- Use Alpine-based images for smaller size and faster pulls
- Add health checks to detect unhealthy containers
- Set resource limits to prevent one container from starving others
- Use Docker's built-in restart policies instead of external process managers
- Keep images small - multi-stage builds remove build dependencies
Docker Compose on a VPS is production-ready for most applications. Combined with proper monitoring and backup procedures, it provides a reliable and maintainable deployment platform.
