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

  1. Concurrent write terbatas — hanya satu proses yang bisa menulis sekaligus

  2. Database locked — sering muncul pas traffic naik

  3. Tidak ada connection pooling — setiap request bikin koneksi baru

  4. Backup terbatas — tidak ada WAL (Write-Ahead Logging) yang robust

Keuntungan PostgreSQL

  1. Concurrent access — ratusan koneksi sekaligus

  2. Connection pooling — efisien untuk aplikasi production

  3. ACID compliance — lebih andal untuk transaksi

  4. Advanced features — JSON support, full-text search, replication

  5. Ecosystem yang mature — banyak tool, monitoring, backup

Kapan Harus Migrasi?

  • Traffic > 100 concurrent users

  • Sering dapat error SQLITE_BUSY

  • Butuh 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_on dengan condition: service_healthy — Next.js baru start kalau PostgreSQL sudah siap - Volume postgres_data untuk 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-deps biar 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.