Docker Compose for VPS: How to Orchestrate Multi-Container Applications with Minimal RAM

Running multiple containers on a budget VPS (1–2 GB RAM) requires careful resource planning. Docker Compose makes orchestration straightforward, but default configurations can quickly exhaust memory. This guide shows how to deploy a full multi-container stack — Nginx reverse proxy, PHP-FPM, PostgreSQL, and Redis — on a VPS with only 1 GB RAM, using Docker Compose resource limits, health checks, and Alpine-based images.

Understanding why resource efficiency matters is the first step. Check our VPS comparison table for plans that balance RAM and cost for containerized workloads.

Why Docker Compose on a Budget VPS?

Docker Compose allows you to define and run multi-container applications with a single docker-compose.yml file. On a budget VPS, the challenge is fitting all services within limited RAM without swapping. Key strategies include:

  • Using Alpine-based images (20–50 MB vs. 200–600 MB for Debian/Ubuntu base)
  • Setting explicit mem_limit and mem_reservation per service
  • Disabling unnecessary services (e.g., PHP opcache file cache if using Redis)
  • Sharing volumes and networks to reduce overhead

Step 1: Install Docker and Docker Compose

On a fresh Ubuntu 22.04 VPS:

sudo apt update
sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
docker --version
docker compose version

Step 2: Define the Minimal Stack

Here’s a docker-compose.yml configured for a 1 GB RAM VPS:

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    mem_limit: 128m
    mem_reservation: 64m
    ports:
      - "80:80"
    volumes:
      - ./app:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      php:
        condition: service_healthy
    networks:
      - app-net

  php:
    image: php:8.3-fpm-alpine
    mem_limit: 256m
    mem_reservation: 128m
    volumes:
      - ./app:/var/www/html
    environment:
      - PHP_MEMORY_LIMIT=128M
      - PHP_MAX_EXECUTION_TIME=30
    healthcheck:
      test: ["CMD", "php-fpm", "-t"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-net

  postgres:
    image: postgres:16-alpine
    mem_limit: 256m
    mem_reservation: 128m
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: changeme
      POSTGRES_SHARED_BUFFERS: 64MB
      POSTGRES_EFFECTIVE_CACHE_SIZE: 192MB
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-net

  redis:
    image: redis:7-alpine
    mem_limit: 64m
    mem_reservation: 32m
    command: redis-server --maxmemory 48mb --maxmemory-policy allkeys-lru
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
    networks:
      - app-net

volumes:
  pgdata:

networks:
  app-net:
    driver: bridge

Total reserved memory: 384 MB | Total limit: 704 MB. This leaves ~300 MB for the kernel, Docker daemon, and headroom — well within a 1 GB VPS.

Step 3: Memory Budget Breakdown

ServiceImage SizeMem LimitMem ReservationCPU Limit (optional)
Nginx (Alpine)23 MB128 MB64 MB0.5
PHP-FPM 8.3 (Alpine)85 MB256 MB128 MB1.0
PostgreSQL 16 (Alpine)157 MB256 MB128 MB1.0
Redis 7 (Alpine)32 MB64 MB32 MB0.5
Total297 MB704 MB352 MB3.0

Compare this to a default Ubuntu-based stack: Nginx (187 MB), PHP (325 MB), PostgreSQL (378 MB), Redis (118 MB) = over 1 GB before any application data. Alpine images save 60–70% of baseline memory.

Step 4: Deploy and Verify

docker compose up -d
docker compose ps
docker stats --no-stream

The docker stats command shows real-time memory usage per container. With the limits above, the entire stack should idle around 400–500 MB. If you’re close to your VPS’s RAM ceiling, increase swappiness to 10 (as covered in our swap optimization guide) and check our performance benchmarks for VPS plans with faster storage to reduce swap latency.

Step 5: Production Readiness

  • Persistent storage: Named volumes (like pgdata) survive container rebuilds. Always use them for databases.
  • Health checks: The depends_on: condition: service_healthy ensures services start in the correct order.
  • Restart policy: Add restart: unless-stopped to each service for automatic recovery after crashes or VPS reboots.
  • Resource limits: Without mem_limit, a memory leak in one container can starve all others. Always set limits.

For even tighter memory budgets, consider replacing PostgreSQL with SQLite (file-based, zero daemon overhead) or using a single Alpine container with multiple processes via supervisord. However, Docker Compose is the recommended approach for maintainability. See our VPS comparison table for plans optimized for containerized workloads.

Leave a Reply