Skip to content

Multi-Container Template

This template is for deploying multiple services together (app + database, app + cache, etc.).

Use Cases

  • Web app with PostgreSQL database
  • API with Redis cache
  • Full-stack application with multiple services
  • Microservices architecture

Template

docker-compose.yml
version: '3.8'

services:
  # Your application
  app:
    build: .
    container_name: my-app
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydb
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    labels:
      # Enable Traefik for this container
      - traefik.enable=true

      # Router configuration
      - traefik.http.routers.my-app.rule=Host(`app.egygeeks.com`)
      - traefik.http.routers.my-app.entrypoints=websecure
      - traefik.http.routers.my-app.tls.certresolver=letsencrypt

      # Service configuration (port your app listens on)
      - traefik.http.services.my-app.loadbalancer.server.port=3000
    networks:
      - traefik_public
      - internal

  # PostgreSQL Database
  db:
    image: postgres:15-alpine
    container_name: my-app-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - internal

  # Redis Cache (optional)
  cache:
    image: redis:7-alpine
    container_name: my-app-cache
    restart: unless-stopped
    networks:
      - internal

volumes:
  postgres-data:

networks:
  traefik_public:
    external: true
  internal:
    driver: bridge

Customization Steps

1. Replace App Name

Find and replace my-app throughout:

  • Container names: my-app, my-app-db, my-app-cache
  • Router names: my-app
  • Service names: my-app

2. Set Your Domain

- traefik.http.routers.my-app.rule=Host(`yourdomain.com`)

3. Configure Database

Change database credentials:

Never use default passwords in production!

Replace password with a strong password. Store in .env file on server.

environment:
  - POSTGRES_PASSWORD=your-strong-password-here

Update connection string:

- DATABASE_URL=postgresql://postgres:your-strong-password-here@db:5432/mydb

4. Remove Optional Services

Don't need Redis? Remove the cache service:

# Remove this entire section
cache:
  image: redis:7-alpine
  ...

And remove from app dependencies:

depends_on:
  - db
  # Remove: - cache

Network Architecture

This template uses two networks for security:

traefik_public (External)

  • Only the app service connects here
  • Allows Traefik to route traffic to your app
  • External network shared across all apps

internal (Private)

  • All services connect here
  • App communicates with database/cache
  • Never exposed to the internet
  • Database and cache are NOT accessible externally
Internet → Traefik → [traefik_public] → App → [internal] → Database/Cache

Security Best Practice

Only your application service should be on traefik_public. Never expose databases or caches directly to Traefik.

Database Options

PostgreSQL (Default)

db:
  image: postgres:15-alpine
  environment:
    - POSTGRES_DB=mydb
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=password
  volumes:
    - postgres-data:/var/lib/postgresql/data

MySQL

db:
  image: mysql:8-debian
  environment:
    - MYSQL_DATABASE=mydb
    - MYSQL_USER=user
    - MYSQL_PASSWORD=password
    - MYSQL_ROOT_PASSWORD=rootpassword
  volumes:
    - mysql-data:/var/lib/mysql

MongoDB

db:
  image: mongo:7
  environment:
    - MONGO_INITDB_DATABASE=mydb
    - MONGO_INITDB_ROOT_USERNAME=admin
    - MONGO_INITDB_ROOT_PASSWORD=password
  volumes:
    - mongo-data:/data/db

Environment Variables

Create .env file on server:

.env
DATABASE_URL=postgresql://postgres:strong-password@db:5432/mydb
REDIS_URL=redis://cache:6379
API_KEY=your-secret-key

Reference in docker-compose.yml:

services:
  app:
    env_file:
      - .env

Never commit .env to git

Add .env to .gitignore. Store secrets only on the server.

Complete Example: Next.js + PostgreSQL

docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    container_name: nextjs-app
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/appdb
    depends_on:
      - db
    labels:
      - traefik.enable=true
      - traefik.http.routers.nextjs-app.rule=Host(`myapp.egygeeks.com`)
      - traefik.http.routers.nextjs-app.entrypoints=websecure
      - traefik.http.routers.nextjs-app.tls.certresolver=letsencrypt
      - traefik.http.services.nextjs-app.loadbalancer.server.port=3000
    networks:
      - traefik_public
      - internal

  db:
    image: postgres:15-alpine
    container_name: nextjs-app-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=appdb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - internal

volumes:
  postgres-data:

networks:
  traefik_public:
    external: true
  internal:
    driver: bridge

With .env file:

.env
DB_PASSWORD=super-secret-password-123

Deployment

After creating your configuration:

  1. Create .env file on server with secrets
  2. Copy the GitHub Actions workflow template
  3. Push to GitHub
  4. Deployment runs automatically

Or deploy manually on the server:

docker compose up -d

Troubleshooting

App can't connect to database

Check container names match connection string:

# Connection string uses 'db' as hostname
DATABASE_URL=postgresql://postgres:password@db:5432/mydb

# Service must be named 'db'
db:
  image: postgres:15-alpine

Verify both are on same network:

docker network inspect my-app_internal

Database data lost after restart

Ensure volume is configured:

volumes:
  - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:  # Must be declared at bottom

Port conflicts

Each service needs unique ports internally, but don't expose database ports:

# ❌ Don't do this
db:
  ports:
    - "5432:5432"  # Never expose database ports!

# ✅ Do this instead
db:
  # No ports section - only accessible via internal network

Single Container Template Deployment Workflow