Back to Blog

PostgreSQL Docker Compose Guide: Complete Setup Examples

TheDBAdmin Team4 min read
PostgreSQL Docker Compose Guide: Complete Setup Examples

Learn how to set up PostgreSQL with Docker Compose, including various configurations for development, production, and high availability scenarios.

Docker Compose makes it easy to set up and manage PostgreSQL instances in containers. This guide covers various Docker Compose configurations for different use cases.

Table of Contents

  1. Basic Setup
  2. Development Environment
  3. Production Setup
  4. High Availability Setup
  5. Backup and Restore
  6. Monitoring Setup

Basic Setup

Simple PostgreSQL Instance

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_db
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secure_password
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres_data:

With pgAdmin

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_db
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secure_password
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: [email protected]
      PGADMIN_DEFAULT_PASSWORD: pgadmin_password
    ports:
      - "5050:80"
    depends_on:
      - postgres
    restart: unless-stopped

volumes:
  postgres_data:

Development Environment

Multiple PostgreSQL Versions

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres15:
    image: postgres:15
    container_name: postgres15
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev_password
      POSTGRES_DB: dev_db
    ports:
      - "5432:5432"
    volumes:
      - postgres15_data:/var/lib/postgresql/data

  postgres16:
    image: postgres:16
    container_name: postgres16
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev_password
      POSTGRES_DB: dev_db
    ports:
      - "5433:5432"
    volumes:
      - postgres16_data:/var/lib/postgresql/data

volumes:
  postgres15_data:
  postgres16_data:

With Custom Configuration

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_dev
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev_password
      POSTGRES_DB: dev_db
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./postgresql.conf:/etc/postgresql/postgresql.conf
      - ./pg_hba.conf:/etc/postgresql/pg_hba.conf
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    
volumes:
  postgres_data:

# postgresql.conf
max_connections = 100
shared_buffers = 256MB
work_mem = 16MB
maintenance_work_mem = 64MB
effective_cache_size = 768MB

Production Setup

Secure Production Configuration

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_prod
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_INITDB_ARGS: "--data-checksums"
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./config:/etc/postgresql
      - ./backup:/backup
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
    restart: unless-stopped
    networks:
      - postgres_net

networks:
  postgres_net:
    driver: bridge

volumes:
  postgres_data:
    driver: local

With SSL

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_ssl
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_INITDB_ARGS: "--data-checksums"
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./ssl/server.crt:/var/lib/postgresql/server.crt:ro
      - ./ssl/server.key:/var/lib/postgresql/server.key:ro
      - ./ssl/root.crt:/var/lib/postgresql/root.crt:ro
    command: >
      postgres
      -c ssl=on
      -c ssl_cert_file=/var/lib/postgresql/server.crt
      -c ssl_key_file=/var/lib/postgresql/server.key
      -c ssl_ca_file=/var/lib/postgresql/root.crt

volumes:
  postgres_data:

High Availability Setup

Primary-Replica Configuration

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres_primary:
    image: postgres:16
    container_name: postgres_primary
    environment:
      POSTGRES_USER: replication_user
      POSTGRES_PASSWORD: replication_password
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - postgres_primary_data:/var/lib/postgresql/data
    command: >
      postgres
      -c wal_level=replica
      -c max_wal_senders=10
      -c max_replication_slots=10

  postgres_replica:
    image: postgres:16
    container_name: postgres_replica
    environment:
      POSTGRES_USER: replication_user
      POSTGRES_PASSWORD: replication_password
      POSTGRES_DB: myapp
    ports:
      - "5433:5432"
    volumes:
      - postgres_replica_data:/var/lib/postgresql/data
    command: >
      postgres
      -c hot_standby=on
      -c primary_conninfo='host=postgres_primary port=5432 user=replication_user password=replication_password'
      -c promote_trigger_file='/var/lib/postgresql/data/promote_replica'
    depends_on:
      - postgres_primary

volumes:
  postgres_primary_data:
  postgres_replica_data:

Backup and Restore

Automated Backup Configuration

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_backup
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backup:/backup

  backup:
    image: postgres:16
    container_name: postgres_backup_service
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      BACKUP_SCHEDULE: "0 0 * * *"  # Daily at midnight
    volumes:
      - ./backup:/backup
      - ./backup-script.sh:/backup-script.sh
    entrypoint: ["/bin/bash", "/backup-script.sh"]
    depends_on:
      - postgres

volumes:
  postgres_data:
bash
# backup-script.sh
#!/bin/bash

while true; do
    # Wait for the next scheduled backup time
    sleep $(($(date -f - <<< "$(echo "$BACKUP_SCHEDULE" | cut -d' ' -f1-5) tomorrow" +%s) - $(date +%s)))
    
    # Create backup filename with timestamp
    BACKUP_FILE="/backup/backup_$(date +%Y%m%d_%H%M%S).sql"
    
    # Perform backup
    pg_dump -h $POSTGRES_HOST -U $POSTGRES_USER -d $POSTGRES_DB -F c -f $BACKUP_FILE
    
    # Remove backups older than 7 days
    find /backup -name "backup_*.sql" -mtime +7 -delete
done

Monitoring Setup

With Prometheus and Grafana

yaml
# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:16
    container_name: postgres_monitored
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data

  postgres_exporter:
    image: prometheuscommunity/postgres-exporter
    container_name: postgres_exporter
    environment:
      DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable"
    ports:
      - "9187:9187"
    depends_on:
      - postgres

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    depends_on:
      - postgres_exporter

  grafana:
    image: grafana/grafana
    container_name: grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    depends_on:
      - prometheus

volumes:
  postgres_data:
  prometheus_data:
  grafana_data:
yaml
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres_exporter:9187']

Best Practices

  1. Security

    • Never expose PostgreSQL ports directly to the internet
    • Use environment variables for sensitive data
    • Enable SSL for production environments
    • Regularly update PostgreSQL images
  2. Performance

    • Configure appropriate resource limits
    • Use volume mounts for persistence
    • Configure PostgreSQL parameters based on available resources
  3. Monitoring

    • Implement health checks
    • Set up monitoring and alerting
    • Configure log rotation
  4. Backup

    • Regular automated backups
    • Test restore procedures
    • Maintain backup retention policy

Further Reading

Remember to adjust these configurations based on your specific needs and always test thoroughly in a development environment before deploying to production.

Written by

TheDBAdmin Team

KEEP LEARNING WITH THEDBADMIN

Turn this guide into a skill you can prove

Keep reading

Featured course · New batch

NextGen DBA + AI Engineer Bootcamp

Instructor-led, lab-first training for production DBAs: PostgreSQL, MongoDB, Docker, Kubernetes and AI.

  • Live labs on real execution plans
  • Backup, PITR & HA in production
  • pgvector, RAG & AI for DBAs

🛒 FROM THE SHOP

PostgreSQL DBA with Docker is a hands-on student book

PDF · 35 pages

₹99₹5,00098% off

View product