Kebanyakan tutorial PostgreSQL di Docker berhenti di docker run postgres. Itu kasih kamu database, tapi bukan yang kamu percaya dengan data production. Nggak ada persistent volume, nggak ada backup, config default dengan shared buffers 128MB, dan connection limit cuma satu.
Artikel ini menjelaskan cara setup PostgreSQL di Docker dengan config yang beneran kuat di bawah load: persistent storage, backup otomatis, connection pooling, dan performance tuning berdasarkan resource server kamu.
Prerequisites
- Docker Engine 24+ dan Docker Compose v2
- Server Linux dengan minimal 2GB RAM (4GB+ direkomendasikan untuk production)
- Cukup disk space buat database dan backup
Langkah 1: Struktur Project
mkdir -p pg-docker && cd pg-docker
mkdir -p data backups config
Struktur:
pg-docker/
docker-compose.yml
config/
postgresql.conf # performance tuning
pg_hba.conf # aturan autentikasi
data/ # penyimpanan database persisten
backups/ # output backup otomatis
Langkah 2: Tulis File Docker Compose
Buat docker-compose.yml:
services:
postgres:
image: postgres:18.4-alpine
container_name: postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: changeme-strong-password-here
POSTGRES_DB: myapp
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./data:/var/lib/postgresql/data
- ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro
- ./config/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
- ./backups:/backups
command: postgres -c config_file=/etc/postgresql/postgresql.conf -c hba_file=/etc/postgresql/pg_hba.conf
shm_size: '256mb'
Yang perlu diperhatikan:
PGDATAgeser direktori data ke subdirektori. Ini mencegah PostgreSQL menolak inisialisasi kalau volume tidak kosong.- Override
commandbilang PostgreSQL buat pake config custom kita alih-alih default. shm_sizeatur limit shared memory. PostgreSQL pakai shared memory buat buffer pool. Default 64MB terlalu kecil buat apa pun selain testing.- Mount volume
backupsbikin backup script bisa diakses di dalam container.
Langkah 3: Tulis Config PostgreSQL
Buat config/postgresql.conf. Ini config yang sudah di-tuning untuk server dengan 4GB RAM:
# Pengaturan koneksi
listen_addresses = '*'
max_connections = 100
# Pengaturan memory (di-tuning untuk server 4GB RAM)
shared_buffers = 1GB
effective_cache_size = 3GB
work_mem = 16MB
maintenance_work_mem = 512MB
# Pengaturan WAL
wal_buffers = 64MB
checkpoint_completion_target = 0.9
wal_compression = lz4
# Perencanaan query
random_page_cost = 1.1
effective_io_concurrency = 200
# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000
log_checkpoints = on
log_connections = on
log_disconnections = on
# Autovacuum
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min
Cara tune ini untuk server kamu:
| Setting | Rumus | Contoh (4GB RAM) |
|---|---|---|
| shared_buffers | 25% RAM | 1GB |
| effective_cache_size | 75% RAM | 3GB |
| work_mem | RAM / max_connections / 4 | 16MB (4096/100/4) |
| maintenance_work_mem | 5-10% RAM | 512MB |
Untuk server 8GB, gandakan shared_buffers jadi 2GB, effective_cache_size jadi 6GB, dan pertahankan work_mem proporsional.
Langkah 4: Tulis Config Autentikasi
Buat config/pg_hba.conf:
# TYPE DATABASE USER ADDRESS METHOD
local all all scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
host myapp appuser 172.16.0.0/12 scram-sha-256
host all all 0.0.0.0/0 reject
Config ini:
- Mewajibkan autentikasi password (scram-sha-256) untuk semua koneksi
- Memperbolehkan user app dari Docker network (172.16.0.0/12 adalah range default)
- Menolak semua koneksi remote lainnya
Sesuaikan range CIDR kalau Docker network-mu pakai subnet yang berbeda. Cek pakai docker network inspect <network_name>.
Langkah 5: Jalankan PostgreSQL
docker compose up -d
Pastikan jalan:
docker compose ps
docker compose logs postgres | tail -5
Log output yang diharapkan:
LOG: starting PostgreSQL 18.4 on x86_64-pc-linux-musl
LOG: listening on IPv4 address "0.0.0.0", port 5432
LOG: database system is ready to accept connections
Test koneksi:
docker compose exec postgres psql -U appuser -d myapp -c "SELECT version();"
Kamu harusnya lihat versi PostgreSQL dan info sistem.
Langkah 6: Bikin Schema Berguna
Connect dan bikin test table:
docker compose exec postgres psql -U appuser -d myapp
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users (email);
INSERT INTO users (email, name) VALUES
('[email protected]', 'Alice'),
('[email protected]', 'Bob');
SELECT * FROM users;
Ketik q buat keluar dari psql.
Langkah 7: Setup Backup Otomatis
Buat backups/backup.sh:
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/myapp_$TIMESTAMP.sql.gz"
# Jalankan pg_dump dan kompres
pg_dump -U appuser -d myapp --no-owner --no-privileges | gzip > "$BACKUP_FILE"
# Hapus backup lebih tua dari 7 hari
find "$BACKUP_DIR" -name "myapp_*.sql.gz" -mtime +7 -delete
echo "Backup selesai: $BACKUP_FILE"
Buat executable dan test:
chmod +x backups/backup.sh
docker compose exec postgres bash /backups/backup.sh
Pastikan backup terbuat:
ls -la backups/
Otomasi Backup dengan Cron
Jalankan backup harian jam 2 pagi pakai crontab host:
crontab -e
Tambah:
0 2 * * * cd /path/to/pg-docker && docker compose exec -T postgres bash /backups/backup.sh >> /var/log/pg-backup.log 2>&1
Flag -T mengalokasikan pseudo-TTY tanpa stdin, yang mencegah cron hanging.
Restore dari Backup
# Lihat backup yang tersedia
ls backups/
# Restore backup tertentu
gunzip -c backups/myapp_20260617_020000.sql.gz | docker compose exec -T postgres psql -U appuser -d myapp
# Atau restore ke database baru
docker compose exec postgres psql -U appuser -c "CREATE DATABASE myapp_restored;"
gunzip -c backups/myapp_20260617_020000.sql.gz | docker compose exec -T postgres psql -U appuser -d myapp_restored
Langkah 8: Tambah pg_stat_statements buat Monitoring
pg_stat_statements lacak performa query. Tambah ke config kamu.
Update config/postgresql.conf:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
Restart PostgreSQL:
docker compose restart postgres
Aktifkan extension:
docker compose exec postgres psql -U appuser -d myapp -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"
Cek query lambat:
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
Langkah 9: Connection Pooling dengan PgBouncer
Kalau aplikasi kamu buka banyak koneksi singkat, connection pooler cegah PostgreSQL buang resource buat connection overhead.
Tambah PgBouncer ke docker-compose.yml:
services:
postgres:
image: postgres:18.4-alpine
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: changeme-strong-password-here
POSTGRES_DB: myapp
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./data:/var/lib/postgresql/data
- ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro
- ./config/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
command: postgres -c config_file=/etc/postgresql/postgresql.conf -c hba_file=/etc/postgresql/pg_hba.conf
shm_size: '256mb'
networks:
- backend
pgbouncer:
image: edoburu/pgbouncer:1.23.1
container_name: pgbouncer
restart: unless-stopped
ports:
- "6432:6432"
environment:
DATABASE_URL: postgres://appuser:***@postgres:5432/myapp
POOL_MODE: transaction
MAX_CLIENT_CONN: 1000
DEFAULT_POOL_SIZE: 20
depends_on:
- postgres
networks:
- backend
networks:
backend:
driver: bridge
Setting PgBouncer yang penting:
POOL_MODE: transactionkembalikan koneksi ke pool setiap transaksi alih-alih setiap sesi. Ini bikin 20 koneksi pool bisa serve ratusan client.MAX_CLIENT_CONNadalah maksimum koneksi client yang diterima PgBouncer.DEFAULT_POOL_SIZEadalah berapa koneksi PgBouncer buka ke PostgreSQL per pasang user/database.
Aplikasi kamu connect ke PgBouncer di port 6432 alih-alih PostgreSQL di 5432. Sisanya tetap sama.
Masalah yang Sering Muncul
PostgreSQL menolak start dengan "data directory has wrong ownership". Direktori host (./data) punya permission yang salah. Perbaiki:
sudo chown -R 999:999 ./data
UID 999 adalah user postgres default di image berbasis Alpine.
"FATAL: could not create shared memory segment" shm_size di Docker Compose terlalu kecil buat setting shared_buffers kamu. Samakan: kalau shared_buffers 1GB, shm_size minimal 1GB.
Backup ukurannya besar. pg_dump tanpa kompresi hasilin file besar. Script backup di atas pakai gzip. Untuk kompresi lebih baik, pakai pg_dump --format=custom (hasilin file .dump yang bisa dibaca pg_restore):
pg_dump -U appuser -d myapp --format=custom --file=/backups/myapp_$TIMESTAMP.dump
Koneksi ditolak dari container aplikasi. Container app-mu belum di jaringan Docker yang sama dengan PostgreSQL. Tambah networks: [backend] ke kedua service, atau pakai host network buat setup yang lebih simpel.
Query lambat setelah pindah ke Docker. Cek shared_buffers dan effective_cache_size. Config default PostgreSQL asumsi jalan di bare metal dengan akses penuh ke RAM. Di Docker, kamu harus set ini secara eksplisit berdasarkan memory limit container.
Langkah Selanjutnya
- Tambah dashboard Grafana + pg_stat_statements buat visualisasi performa query
- Setup WAL archiving buat point-in-time recovery
- Pakai pg_basebackup buat full physical backup (restore lebih cepat dari pg_dump buat database besar)
- Pertimbangkan pgvector kalau kamu butuh vector search buat workload AI/ML