A production-ready PostgreSQL 16 replication environment with monitoring, management tools, and automatic failover capabilities.
- PostgreSQL Version: 16-alpine
- Replication Type: Asynchronous streaming replication
- Health: Both primary and replica operational
- Replication Lag: < 3ms (near real-time)
- Data Persistence: Docker volumes for data safety
# 1. Clone or download this repository
git clone <your-repo-url>
cd self-hosted-pg
# 2. Configure environment (IMPORTANT: Change passwords!)
cp .env.postgres.example .env.postgres
nano .env.postgres
# 3. Start the cluster
./manage-postgres.sh start
# 4. Verify replication is working
./manage-postgres.sh testself-hosted-pg/
βββ docker-compose-postgres.yml # Main orchestration file
βββ manage-postgres.sh # Management CLI tool
βββ .env.postgres # Environment variables (create from .example)
βββ postgres/
β βββ primary/
β β βββ init/ # Initialization scripts (01-init-primary.sh)
β β βββ config/ # postgresql.conf, pg_hba.conf
β βββ replica/
β β βββ config/ # Replica-specific configuration
β βββ scripts/
β β βββ setup-replica.sh # Replica initialization script
β β βββ monitor-replication.sh
β βββ pgadmin/
β βββ servers.json # PgAdmin server configurations
βββ backups/ # Database backups directory
| Service | Container Name | Port | Purpose |
|---|---|---|---|
| PostgreSQL Primary | postgres_primary | 5432 | Read/Write operations |
| PostgreSQL Replica | postgres_replica | 5433 | Read-only operations |
| PgAdmin 4 | pgadmin | 5050 | Web-based management UI |
./manage-postgres.sh start # Start all services
./manage-postgres.sh stop # Stop all services
./manage-postgres.sh restart # Restart all services
./manage-postgres.sh status # Show cluster status./manage-postgres.sh monitor # Detailed replication metrics
./manage-postgres.sh test # Test replication is working
./manage-postgres.sh logs all # View all service logs
./manage-postgres.sh logs primary # Primary logs only
./manage-postgres.sh logs replica # Replica logs only./manage-postgres.sh psql primary # Connect to primary (read/write)
./manage-postgres.sh psql replica # Connect to replica (read-only)
./manage-postgres.sh backup # Create full backup
./manage-postgres.sh restore <file> # Restore from backup./manage-postgres.sh promote # Promote replica to primary (failover)
./manage-postgres.sh clean # Remove all data (WARNING: Data loss!)# Check replication status
docker exec postgres_primary psql -U postgres -c "SELECT * FROM pg_stat_replication;"
# Check replica lag
docker exec postgres_primary psql -U postgres -c "
SELECT application_name, state, sync_state, replay_lag
FROM pg_stat_replication;"docker exec postgres_replica psql -U postgres -c "SELECT pg_is_in_recovery();"
# Should return 't' (true)Edit .env.postgres:
POSTGRES_PASSWORD=<strong-password>
REPLICATION_PASSWORD=<strong-password>
PGADMIN_PASSWORD=<strong-password>The setup uses Docker networks for isolation. For production:
- Configure firewall rules
- Use SSL/TLS certificates
- Implement network segmentation
Current pg_hba.conf uses MD5 authentication for network connections.
# Primary (Read/Write)
postgresql://postgres:password@localhost:5432/myapp
# Replica (Read-Only)
postgresql://postgres:password@localhost:5433/myappservices:
your-app:
environment:
- DB_HOST=postgres-primary
- DB_PORT=5432
- DB_NAME=myapp
- DB_USER=postgres
- DB_PASSWORD=${POSTGRES_PASSWORD}
networks:
- self-hosted-pg_postgres_network
networks:
self-hosted-pg_postgres_network:
external: trueconst { Pool } = require('pg');
// Write operations
const writePool = new Pool({
host: 'localhost',
port: 5432,
database: 'myapp',
user: 'postgres',
password: process.env.POSTGRES_PASSWORD
});
// Read operations
const readPool = new Pool({
host: 'localhost',
port: 5433,
database: 'myapp',
user: 'postgres',
password: process.env.POSTGRES_PASSWORD
});shared_buffers: 256MBeffective_cache_size: 1GBmax_connections: 200wal_keep_size: 1GB
For production, adjust based on available RAM:
shared_buffers = 25% of RAM
effective_cache_size = 75% of RAM
work_mem = RAM / max_connections / 4
# Check replica logs
docker logs postgres_replica
# Verify replication user exists
docker exec postgres_primary psql -U postgres -c "\du replicator"
# Test connectivity
docker exec postgres_replica ping postgres-primary# Check current lag
./manage-postgres.sh monitor
# Force checkpoint
docker exec postgres_primary psql -U postgres -c "CHECKPOINT;"# Stop services
./manage-postgres.sh stop
# Remove replica volume
docker volume rm self-hosted-pg_postgres_replica_data
# Restart (replica will resync)
./manage-postgres.sh start# Add to crontab for daily backups at 2 AM
0 2 * * * cd /path/to/self-hosted-pg && ./manage-postgres.sh backup# Create backup
./manage-postgres.sh backup
# List backups
ls -la backups/
# Restore specific backup
./manage-postgres.sh restore backups/postgres_backup_20250912_120613.sql- Docker Engine 20.10+
- Docker Compose 1.29+
- 2GB+ RAM recommended
- 10GB+ disk space
Both services include health checks:
- Primary: Every 10s, checks if PostgreSQL is accepting connections
- Replica: Every 10s, verifies PostgreSQL is running
- Replication is asynchronous by default (can be made synchronous)
- Replica is read-only (cannot accept writes)
- WAL files accumulate - monitor disk space
- Default database name is
myapp - PgAdmin requires email/password from
.env.postgres
# If you see "root execution not permitted"
chmod +x fix-monitor.sh
./fix-monitor.sh# Complete reset (WARNING: Deletes all data!)
./manage-postgres.sh clean
./manage-postgres.sh setup
./manage-postgres.sh startWhen everything is working correctly:
./manage-postgres.sh statusshows both containers healthy./manage-postgres.sh testconfirms replication works./manage-postgres.sh monitorshows streaming state with minimal lag- PgAdmin accessible at http://localhost:5050
Version: PostgreSQL 16 with Streaming Replication
Last Updated: September 2025
Status: Production-ready for development/testing environments
