Migrasi dari SQLite ke PostgreSQL untuk Next.js + Docker — Panduan Praktis untuk Developer Indonesia
Update 2026: Artikel ini sudah diuji langsung di VPS Ubuntu 24.04. Cocok untuk developer yang sudah pernah deploy Next.js + Docker sebelumnya.
Pendahuluan
Hai developer! Di artikel sebelumnya, kita sudah bahas cara deploy Next.js + Prisma + SQLite ke Docker dengan Caddy. Artikel ini sudah cukup bagus untuk MVP, portfolio, atau aplikasi kecil.
Tapi, ada saatnya kamu merasa aplikasimu mulai "nge-lock" atau lambat pas traffic naik. Itu pertanda kamu perlu migrasi ke PostgreSQL.
Kenapa? Karena SQLite tidak dirancang untuk concurrent write. Pas banyak user yang akses bareng, kamu bakal lihat error SQLITE_BUSY: database is locked di log.
Di artikel ini, kita bakal bahas cara migrasi dari SQLite ke PostgreSQL dengan zero downtime. Simpel, praktis, dan bisa langsung dipraktikkan.
1. Kenapa Migrasi ke PostgreSQL?
Ini dia alasan utamanya:
Keterbatasan SQLite
Concurrent write terbatas — hanya satu proses yang bisa menulis sekaligus
Database locked — sering muncul pas traffic naik
Tidak ada connection pooling — setiap request bikin koneksi baru
Backup terbatas — tidak ada WAL (Write-Ahead Logging) yang robust
Keuntungan PostgreSQL
Concurrent access — ratusan koneksi sekaligus
Connection pooling — efisien untuk aplikasi production
ACID compliance — lebih andal untuk transaksi
Advanced features — JSON support, full-text search, replication
Ecosystem yang mature — banyak tool, monitoring, backup
Kapan Harus Migrasi?
Traffic > 100 concurrent users
Sering dapat error
SQLITE_BUSYButuh fitur advanced (JSON, full-text search)
Aplikasi sudah produksi dan stabil
2. Backup Database SQLite
Sebelum migrasi, selalu backup database dulu. Ini langkah kritis — jangan dilewati!
Backup Manual
# Masuk ke container Next.js
docker-compose exec next-app sh
# Copy database file
cp prisma/dev.db prisma/dev.backup.$(date +%Y%m%d).db
# Keluar
exit
# Copy ke host
docker-compose cp next-app:/app/prisma/dev.backup.20260723.db ./backup/
Backup via Cron Job
# Tambahkan ke crontab untuk backup otomatis
echo "0 2 * * * cd /path/to/app && docker-compose exec -T next-app cp prisma/dev.db prisma/backup/dev.\$(date +\\%Y\\%m\\%d).db" | crontab -
Catatan: Pastikan kamu punya backup yang valid sebelum mulai migrasi. Test restore-nya juga!
3. Setup PostgreSQL di Docker
Kita akan tambahkan service PostgreSQL di docker-compose.yml. Berikut update-nya:
docker-compose.yml (Updated)
version: "3.8"
services:
next-app:
build:
context: .
dockerfile: Dockerfile
container_name: next-app
restart: unless-stopped
env_file:
- .env.local
volumes:
- ./prisma/migrations:/app/prisma/migrations
networks:
- web
expose:
- "3000"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-nextuser}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-nextpass}
POSTGRES_DB: ${POSTGRES_DB:-nextdb}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
networks:
- web
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-nextuser}"]
interval: 10s
timeout: 5s
retries: 5
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- web
networks:
web:
external: false
volumes:
postgres_data:
caddy_data:
caddy_config:
Perubahan penting: - Tambah service
postgres-depends_ondengancondition: service_healthy— Next.js baru start kalau PostgreSQL sudah siap - Volumepostgres_datauntuk persistensi data - Health check untuk PostgreSQL
4. Update Environment Variables
Update file .env.local untuk PostgreSQL:
# Database
DATABASE_URL="postgresql://nextuser:nextpass@postgres:5432/nextdb?schema=public"
# Next.js
NEXTAUTH_URL="https://domainkamu.com"
NEXTAUTH_SECRET="ganti-dengan-random-string-yang-panjang"
NODE_ENV=production
# PostgreSQL (untuk Docker)
POSTGRES_USER="nextuser"
POSTGRES_PASSWORD="nextpass"
POSTGRES_DB="nextdb"
Kenapa pakai
postgres:5432? Karena kita pakai Docker network, service bisa saling komunikasi pakai nama container (postgres).
5. Update Prisma Schema
Buka file prisma/schema.prisma dan ganti provider dari SQLite ke PostgreSQL:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Hanya ganti
provider— model dan field tetap sama. Prisma akan handle perbedaan dialect di level query.
Setelah itu, generate Prisma client baru:
# Di dalam container atau di development
npx prisma generate
# Atau di Docker
docker-compose exec next-app npx prisma generate
6. Migrasi Data dari SQLite ke PostgreSQL
Ini bagian paling krusial. Ada dua cara:
Cara 1: Pakai pgloader (Recommended)
pgloader adalah tool khusus untuk migrasi database. Berikut cara pakainya:
Setup pgloader di Docker
# Jalankan pgloader di container terpisah
docker run --rm -it \
--network=host \
dimitri/pgloader \
pgloader \
sqlite:///home/ubuntu/prisma/dev.db \
postgresql://nextuser:nextpass@localhost:5432/nextdb
Catatan: pgloader perlu akses ke file SQLite di host. Pastikan path-nya benar.
Migration dengan pgloader (langkah demi langkah)
# 1. Export database SQLite ke file
docker-compose exec next-app sh -c "sqlite3 prisma/dev.db .dump > /tmp/sqlite_dump.sql"
# 2. Copy ke host
docker-compose cp next-app:/tmp/sqlite_dump.sql ./
# 3. Jalankan pgloader
docker run --rm -it \
-v $(pwd):/data \
--network=host \
dimitri/pgloader \
pgloader \
sqlite:///data/sqlite_dump.sql \
postgresql://nextuser:nextpass@localhost:5432/nextdb
Cara 2: Manual Export/Import
Jika pgloader tidak tersedia, gunakan cara manual:
# 1. Export data dari SQLite
docker-compose exec next-app sh -c "npx prisma db pull && npx prisma generate"
# 2. Export ke JSON
docker-compose exec next-app node -e "
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function exportData() {
const users = await prisma.user.findMany();
require('fs').writeFileSync('/tmp/users.json', JSON.stringify(users, null, 2));
console.log('Exported', users.length, 'users');
}
exportData().catch(console.error);
"
# 3. Copy ke host
docker-compose cp next-app:/tmp/users.json ./
# 4. Import ke PostgreSQL
docker-compose exec next-app node -e "
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const users = require('/tmp/users.json');
async function importData() {
for (const user of users) {
await prisma.user.create({ data: user });
}
console.log('Imported', users.length, 'users');
}
importData().catch(console.error);
"
Warning: Cara manual ini lebih lambat dan tidak cocok untuk data besar. Gunakan pgloader untuk production.
7. Zero-Downtime Deployment
Ini teknik penting untuk production — deploy tanpa downtime. Berikut strategi:
Langkah 1: Deploy PostgreSQL paralel
# 1. Tambahkan service PostgreSQL di docker-compose.yml
# 2. Deploy PostgreSQL dulu (tanpa menghentikan Next.js yang lama)
docker-compose up -d postgres
# 3. Tunggu sampai PostgreSQL siap
docker-compose ps
# 4. Migrasi data
# (gunakan pgloader atau cara manual di atas)
Langkah 2: Deploy Next.js baru
# 1. Update Prisma schema ke PostgreSQL
# 2. Update .env.local ke PostgreSQL
# 3. Build ulang Docker image
docker-compose build --no-cache next-app
# 4. Deploy dengan zero downtime
# Skala ke 2 instance dulu
docker-compose up -d --no-deps --scale next-app=2 next-app
# 5. Setelah semua siap, turunkan ke 1
docker-compose up -d --no-deps --scale next-app=1 next-app
Kenapa zero downtime? Karena kita pakai
--no-depsbiar Caddy tidak ikut restart. Traffic tetap dialirkan ke instance yang masih berjalan.
Langkah 3: Verifikasi
# Cek health endpoint
curl -f https://domainkamu.com/api/health
# Cek database connection
docker-compose exec next-app npx prisma db pull
# Cek data
docker-compose exec next-app npx prisma studio
8. Post-Migration Checklist
Setelah migrasi selesai, jangan lupa checklist ini:
✅ Verifikasi Data
[ ] Semua data berhasil migrasi (bandingkan jumlah record)
[ ] Relasi antar tabel masih benar
[ ] Data unik (unique constraint) tidak duplikat
✅ Konfigurasi
[ ] DATABASE_URL mengarah ke PostgreSQL
[ ] Connection pooling sudah dikonfigurasi
[ ] Environment variables sudah benar
✅ Monitoring
[ ] Setup monitoring untuk PostgreSQL (CPU, memory, disk)
[ ] Setup alerting untuk connection count
[ ] Log query lambat (slow query)
✅ Backup
[ ] Setup backup otomatis untuk PostgreSQL
[ ] Test restore database
[ ] Dokumentasi prosedur backup
✅ Performance
[ ] Index yang dibutuhkan sudah dibuat
[ ] Query yang lambat sudah dioptimalkan
[ ] Connection pool size sudah sesuai
Contoh Backup PostgreSQL
# Backup manual
docker-compose exec postgres pg_dump -U nextuser nextdb > backup_$(date +%Y%m%d).sql
# Backup otomatis via cron
echo "0 2 * * * cd /path/to/app && docker-compose exec -T postgres pg_dump -U nextuser nextdb > backup_\$(date +\\%Y\\%m\\%d).sql" | crontab -
Contoh Monitoring dengan pg_stat_statements
# Aktifkan pg_stat_statements di PostgreSQL
docker-compose exec postgres psql -U nextuser -d nextdb -c "
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
"
# Query untuk melihat slow query
docker-compose exec postgres psql -U nextuser -d nextdb -c "
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
"
9. Troubleshooting Migrasi
Error: "relation does not exist"
# Pastikan migrasi sudah dijalankan
docker-compose exec next-app npx prisma migrate deploy
# Atau push schema langsung (development only)
docker-compose exec next-app npx prisma db push
Error: "connection refused"
# Cek PostgreSQL sudah berjalan
docker-compose ps
# Cek network connectivity
docker-compose exec next-app ping postgres
# Cek environment variables
docker-compose exec next-app env | grep DATABASE
Error: "database is locked" (masih ada di log)
# Pastikan tidak ada service lama yang masih pakai SQLite
docker-compose ps
# Restart semua service
docker-compose down
docker-compose up -d
10. Kesimpulan
Nah itu dia panduan migrasi dari SQLite ke PostgreSQL untuk Next.js + Docker. Simpel kan?
Kesimpulan utama: 1. Migrasi ke PostgreSQL wajib ketika traffic naik dan sering dapat error SQLITE_BUSY 2. Backup dulu sebelum migrasi — jangan pernah skip langkah ini 3. Gunakan pgloader untuk migrasi data yang andal 4. Zero-downtime deployment dengan --no-deps dan --scale 5. Post-migration checklist penting untuk memastikan semuanya berjalan dengan baik
Best practices: - Selalu test migrasi di environment staging dulu - Gunakan connection pooling (PgBouncer) untuk traffic tinggi - Setup monitoring dan alerting untuk PostgreSQL - Backup rutin — jangan sampai kehilangan data production
Kalau kamu punya pertanyaan tentang migrasi, jangan ragu tinggalkan komentar. Semoga artikel ini membantu kamu scale aplikasi dengan percaya diri!
FAQ
Q: Berapa lama migrasi biasanya butuh waktu? A: Untuk data kecil (< 1GB), 5-10 menit. Untuk data besar (> 10GB), bisa 1-2 jam tergantung ukuran dan kompleksitas.
Q: Boleh nggak pakai managed PostgreSQL (seperti Supabase atau AWS RDS)? A: Boleh banget! Justru lebih direkomendasikan untuk production. Tinggal ganti DATABASE_URL ke endpoint yang diberikan.
Q: Bagaimana cara kembali ke SQLite jika migrasi gagal? A: Karena kita backup database dulu, tinggal restore backup dan ganti DATABASE_URL kembali ke SQLite.
Q: Perlu nggak update kode aplikasi setelah migrasi? A: Biasanya tidak. Prisma handle perbedaan dialect. Tapi pastikan tidak ada query khusus SQLite yang tidak didukung PostgreSQL.
Artikel ini ditulis oleh developer Indonesia, untuk developer Indonesia. Kalau berguna, jangan lupa share ke teman-teman!
Artikel ini ditulis oleh developer Indonesia, untuk developer Indonesia.




