Setup Local PostgreSQL Development di Mesin Kamu

by Marcus Chen
Setup Local PostgreSQL Development di Mesin Kamu

Kamu lagi ngoding, terus tiba-tiba query hang karena koneksi ke remote database putus. Atau worse — kamu edit data production karena salah environment. Dua skenario itu nyebelin banget, dan keduanya bisa dihindari kalau kamu punya setup local PostgreSQL development yang beres dari awal.

Artikel ini bukan tutorial "install terus next-next-finish". Kita bahas dari install, konfigurasi yang sering kelewat, sampai gotcha yang bikin kamu buang waktu sejam lebih.

Install PostgreSQL: Pilih Cara yang Tepat

Ada tiga cara umum install Postgres di lokal: native package, Docker, dan package manager seperti Homebrew (macOS) atau apt (Linux). Masing-masing ada trade-off-nya.

Native install paling stabil untuk daily driver, tapi susah kalau kamu butuh multiple versi Postgres sekaligus.

Docker fleksibel banget — bisa spin up Postgres 14, 15, 16 sekaligus tanpa konflik. Ini yang paling gue rekomendasiin untuk setup local PostgreSQL development kalau kamu sering ganti-ganti project.

Homebrew / apt paling cepat untuk setup sekali pakai.

Kita pakai Docker karena paling reproducible:

# Pull image Postgres 16
docker pull postgres:16-alpine

# Jalankan container dengan persistent volume
docker run -d \
  --name pg-local \
  -e POSTGRES_USER=devuser \
  -e POSTGRES_PASSWORD=devpass \
  -e POSTGRES_DB=devdb \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16-alpine

Flag -v pgdata:/var/lib/postgresql/data itu penting. Tanpa ini, setiap kali container di-restart, data kamu hilang. Gue pernah kehilangan seed data dua jam gara-gara skip flag ini.

Cek apakah container jalan:

docker ps | grep pg-local

Koneksi Pertama dan Verifikasi Setup

Setelah container jalan, coba koneksi pakai psql. Kalau belum install psql di host machine kamu:

# Ubuntu/Debian
sudo apt install postgresql-client

# macOS dengan Homebrew
brew install libpq
export PATH="/opt/homebrew/opt/libpq/bin:$PATH"

Koneksi ke container:

psql -h localhost -U devuser -d devdb

Kalau berhasil, kamu masuk ke prompt devdb=#. Coba query sederhana:

SELECT version();
SELECT current_database();

Output-nya harusnya nunjukin versi Postgres dan nama database devdb.

Gotcha pertama: kalau kamu dapat error FATAL: password authentication failed, cek apakah environment variable di Docker run sudah benar. Kadang ada typo di nama variable — POSTGRES_PASSWORD bukan POSTGRES_PASS.

Konfigurasi postgresql.conf yang Sering Kelewat

Default config Postgres itu konservatif banget — didesain untuk server production dengan resource terbatas, bukan development machine modern. Untuk lokal, kamu perlu tune beberapa parameter.

Masuk ke container dulu:

docker exec -it pg-local bash

Edit config:

# Di dalam container
vi /var/lib/postgresql/data/postgresql.conf

Parameter yang perlu diubah untuk development:

# Naikkan shared_buffers ke 25% RAM kamu
shared_buffers = 256MB

# Untuk development, matikan fsync biar lebih cepat
# WARNING: jangan ini di production!
fsync = off
synchronous_commit = off

# Log semua query (berguna untuk debugging)
log_statement = 'all'
log_duration = on

# Naikkan max connections
max_connections = 100

Restart container setelah edit:

docker restart pg-local

Gotcha kedua: fsync = off itu oke untuk development karena bikin write jauh lebih cepat, tapi data bisa corrupt kalau container crash tiba-tiba. Jangan pernah pakai ini di production. Gue kasih warning ini karena pernah ada yang copy-paste config development ke server production — jangan.

Setup dengan Docker Compose untuk Project Nyata

Kalau kamu kerja di project yang punya lebih dari satu service (misalnya app + database + Redis), Docker Compose jauh lebih rapi daripada jalanin container satu-satu.

Buat file docker-compose.yml di root project kamu:

version: '3.9'

services:
  db:
    image: postgres:16-alpine
    container_name: ${PROJECT_NAME:-myapp}-db
    environment:
      POSTGRES_USER: ${DB_USER:-devuser}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-devpass}
      POSTGRES_DB: ${DB_NAME:-devdb}
    ports:
      - "${DB_PORT:-5432}:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-devuser} -d ${DB_NAME:-devdb}"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

Buat file .env di direktori yang sama:

PROJECT_NAME=myapp
DB_USER=devuser
DB_PASSWORD=devpass
DB_NAME=devdb
DB_PORT=5432

Kalau kamu mau database langsung punya schema saat pertama kali dijalankan, buat file init.sql:

-- init.sql
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    body TEXT,
    published_at TIMESTAMP
);

-- Seed data untuk development
INSERT INTO users (email) VALUES
    ('alice@example.com'),
    ('bob@example.com')
ON CONFLICT DO NOTHING;

Jalankan:

docker compose up -d

# Cek status
docker compose ps

# Lihat logs
docker compose logs db

File init.sql di-mount ke /docker-entrypoint-initdb.d/ — Postgres akan otomatis eksekusi semua .sql file di folder itu waktu pertama kali database diinisialisasi.

Gotcha ketiga: init.sql hanya dieksekusi kalau volume masih kosong (fresh). Kalau kamu sudah punya volume dari sebelumnya dan mau reset, kamu perlu hapus volume dulu:

docker compose down -v  # flag -v hapus volumes
docker compose up -d

Connection String dan Integrasi ke Aplikasi

Biasanya developer langsung hardcode connection string di kode. Jangan. Pakai environment variable dari awal, bahkan untuk lokal.

Format connection string Postgres:

postgresql://[user]:[password]@[host]:[port]/[dbname]

Contoh untuk setup kita:

postgresql://devuser:devpass@localhost:5432/devdb

Kalau pakai Node.js dengan library pg:

// db.js
const { Pool } = require('pg');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  // Untuk development, matikan SSL
  ssl: process.env.NODE_ENV === 'production' 
    ? { rejectUnauthorized: false } 
    : false,
});

async function query(text, params) {
  const start = Date.now();
  const res = await pool.query(text, params);
  const duration = Date.now() - start;
  
  if (process.env.NODE_ENV !== 'production') {
    console.log('Query executed:', { text, duration, rows: res.rowCount });
  }
  
  return res;
}

module.exports = { query, pool };

File .env untuk aplikasi:

DATABASE_URL=postgresql://devuser:devpass@localhost:5432/devdb
NODE_ENV=development

Kalau pakai Python dengan psycopg2:

import psycopg2
import os
from urllib.parse import urlparse

def get_connection():
    url = urlparse(os.environ['DATABASE_URL'])
    return psycopg2.connect(
        host=url.hostname,
        port=url.port,
        database=url.path[1:],
        user=url.username,
        password=url.password
    )

# Test koneksi
if __name__ == '__main__':
    conn = get_connection()
    cur = conn.cursor()
    cur.execute('SELECT version()')
    print(cur.fetchone())
    conn.close()

pgAdmin vs CLI: Pilih Tool yang Sesuai Workflow

Untuk visualisasi dan explore data, kamu punya beberapa pilihan:

psql CLI — paling cepat kalau kamu sudah familiar. Shortcut penting:

\l          -- list semua database
\dt         -- list semua table di database aktif
\d users    -- describe table users
\timing     -- toggle tampilan query duration
\x          -- expanded display (bagus untuk row dengan banyak kolom)

pgAdmin 4 via Docker kalau kamu prefer GUI:

# Tambahkan ke docker-compose.yml
  pgadmin:
    image: dpage/pgadmin4:latest
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@local.dev
      PGADMIN_DEFAULT_PASSWORD: adminpass
    ports:
      - "5050:80"
    depends_on:
      - db

Akses di http://localhost:5050. Waktu add server, gunakan hostname db (nama service di Compose), bukan localhost.

TablePlus atau DBeaver juga bagus kalau kamu mau desktop app. Gue pribadi pakai TablePlus untuk daily use karena UI-nya clean dan query editor-nya responsif.

Gotcha keempat: kalau pakai pgAdmin via Docker dan koneksi ke Postgres gagal, pastikan kamu pakai nama service (db) bukan localhost sebagai hostname. Ini error klasik yang bikin frustrasi 15 menit pertama.

Backup dan Restore untuk Development

Sering kebutuhan development adalah dump data dari staging/production terus restore ke lokal. Cara yang aman:

# Dump dari remote (ganti dengan credentials staging kamu)
pg_dump \
  --no-owner \
  --no-acl \
  -Fc \
  postgresql://user:pass@staging-host:5432/stagingdb \
  > staging_dump.dump

# Restore ke lokal
pg_restore \
  --no-owner \
  --no-acl \
  -d postgresql://devuser:devpass@localhost:5432/devdb \
  staging_dump.dump

Flag --no-owner dan --no-acl penting supaya restore tidak gagal karena perbedaan user antara staging dan lokal.

Kalau mau anonymize data sensitif sebelum restore (best practice kalau data ada PII), kamu bisa pakai tool seperti postgresql-anonymizer atau script sederhana setelah restore:

-- Anonymize email setelah restore
UPDATE users 
SET email = 'user_' || id || '@example.com'
WHERE email NOT LIKE '%@example.com';

Yang Gue Lakuin Sekarang

Setup gue saat ini: Docker Compose untuk setiap project, dengan docker-compose.yml yang sudah include Postgres + pgAdmin. Template-nya gue simpan di repo tersendiri, jadi kalau mulai project baru tinggal copy dan ganti nama service.

Beberapa hal yang langsung gue lakuin setelah setup local PostgreSQL development:

  1. Buat script db:reset di package.json atau Makefile yang drop + recreate database + run migrations + seed. Ini menghemat waktu kalau schema berubah drastis.
  2. Aktifkan log_statement = 'all' di development config supaya bisa lihat query yang dieksekusi ORM — berguna banget untuk debug N+1 query.
  3. Simpan .env.example di repo tapi .env di .gitignore. Jangan pernah commit credentials ke Git, bahkan credentials lokal.

Kalau kamu mau lanjut, langkah berikutnya yang worth explore: setup pgBouncer untuk connection pooling di lokal (simulasi kondisi production), dan integrasi dengan migration tool seperti Flyway atau golang-migrate supaya schema change bisa di-track dengan proper.