TuBrief
구독 채널
비디오
커뮤니티

Langkah Teknis Beralih dari Firebase ke Self-Hosting $5 per Bulan

TuBrief 편집팀
2026년 7월 14일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

Bahasa Indonesia한국어EnglishEspañol中文العربيةहिन्दीDeutschFrançaisPortuguêsРусский日本語

관련 영상

Alternatif Go untuk Firebase Gratis ini Hanya Berupa Satu File7:49

Alternatif Go untuk Firebase Gratis ini Hanya Berupa Satu File

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Langkah Teknis Beralih dari Firebase ke Self-Hosting $5 per Bulan

Firebase terasa manis di awal. Kita bisa fokus mengimplementasikan fitur tanpa memikirkan infrastruktur. Namun, ketika layanan mulai berkembang sedikit saja, angka pada tagihan mulai membengkak. Di sisi lain, rencana untuk berpindah ke self-hosting justru membuat takut. Bagaimana jika data hilang? Bagaimana jika layanan mati setiap kali melakukan deployment?

Kekhawatiran itu sangat wajar. Namun, ada caranya. Saya telah merangkum cara konkret untuk memindahkan data Firestore ke PocketBase (backend all-in-one berbasis SQLite) dengan aman, serta membangun lingkungan deployment tanpa henti (zero-downtime) menggunakan GitHub Actions dan Nginx.


Pipeline Migrasi Data Firestore ke PocketBase

Salah satu kendala pertama saat memindahkan data tidak terstruktur dari Firestore ke skema PocketBase yang berbasis relasional adalah panjang pengenal (identifier). Firestore menggunakan ID dokumen 20 karakter, sementara PocketBase secara default memberlakukan batasan 15 karakter alfanumerik. Jika Anda mengabaikan panjang ini dan memaksanya masuk, Anda akan melihat error validation_length_invalid.

Untuk menghindari masalah ini, Anda perlu memperpendek panjangnya namun tetap menjaga keunikan. Cara yang paling pasti adalah dengan melakukan hashing ID Firestore menggunakan SHA256, lalu mengambil 15 karakter pertama. Anda tidak perlu khawatir soal tabrakan data (collision). Bahkan dalam dataset besar sekalipun, kemungkinan nilai hash 15 karakter tumpang tindih sangat rendah sehingga bisa diabaikan.

Berikut adalah struktur skrip migrasi Node.js untuk menangani hal ini. Skrip ini menggunakan paket firebase-admin dan axios dengan metode pengambilan data per 100 baris.

`javascript
// migration.js
const admin = require('firebase-admin');
const axios = require('axios');
const crypto = require('crypto');

admin.initializeApp({
credential: admin.credential.applicationDefault()
});

const db = admin.firestore();
const PB_URL = 'http://127.0.0.1:8090/api/collections/posts/records';

async function migrate() {
let lastDoc = null;
let hasMore = true;

while (hasMore) {
let query = db.collection('posts').orderBy('name').limit(100);
if (lastDoc) {
query = query.startAfter(lastDoc);
}

const snapshot = await query.get();
if (snapshot.empty) {
  hasMore = false;
  break;
}

for (const doc of snapshot.docs) {
  const data = doc.data();
  // Mengonversi ID Firestore 20 karakter menjadi 15 karakter alfanumerik
  const newId = crypto.createHash('sha256').update(doc.id).digest('hex').substring(0, 15);
  
  try {
    await axios.post(PB_URL, {
      id: newId,
      firestore_id: doc.id, // Menyimpan ID asli untuk verifikasi jika diperlukan
      title: data.title,
      content: data.content
    });
  } catch (err) {
    console.error(`Gagal memindahkan: ${doc.id}`, err.response?.data);
  }
}
lastDoc = snapshot.docs[snapshot.docs.length - 1];

}
}

migrate();

`

Untuk melakukan proses ini tanpa henti, Anda perlu menggunakan periode transisi. Pertama, gunakan skrip ini untuk memindahkan lebih dari 90% data yang ada di latar belakang. Setelah itu, terapkan kode dual-write di sisi klien untuk menulis ke Firebase dan PocketBase secara bersamaan saat ada operasi tulis. Setelah memastikan data di kedua sisi sinkron, ubah endpoint baca ke PocketBase dan hapus kode Firebase. Dari sisi pengguna, server tidak akan mengalami henti sesaat pun.


Pengaturan Server PocketBase yang Berjalan 24/7

PocketBase adalah backend ringan yang berjalan sebagai biner tunggal. Namun, jika Anda hanya menjalankannya begitu saja di server Linux, tidak ada cara untuk memulihkan layanan jika proses mati karena kehabisan memori (OOM) yang tidak terduga.

Anda perlu mengatur systemd agar proses dapat hidup kembali secara otomatis jika mati. Buat file /etc/systemd/system/pocketbase.service dan daftarkan konten di bawah ini.

`ini
[Unit]
Description=PocketBase Service
After=network.target

[Service]
Type=simple
User=pocketbase
Group=pocketbase
LimitNOFILE=65535
ExecStart=/opt/pocketbase/pocketbase serve --http="127.0.0.1:8090"
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

`

Di sini, pengaturan LimitNOFILE=65535 sangat penting. Saat menggunakan fitur real-time subscription (WebSocket) yang menjadi kekuatan utama PocketBase, pengaturan ini mencegah koneksi terputus akibat batasan file descriptor default Linux ketika ada lonjakan pengguna secara bersamaan.

Di bagian depan, gunakan Nginx untuk menangani sertifikat SSL dan proxy. Anda perlu menambahkan opsi pada konfigurasi /etc/nginx/sites-available/pocketbase agar koneksi real-time stream tidak tertunda.

`nginx
upstream pocketbase {
server 127.0.0.1:8090;
}

server {
server_name api.yourdomain.com;

location / {
    proxy_pass http://pocketbase;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Mendukung WebSocket dan SSE real-time streaming
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_buffering off;
}

}

`

Sekarang, konfigurasi infrastruktur dasar selesai dengan hanya menerapkan sertifikat HTTPS menggunakan perintah certbot --nginx.

Berdasarkan pengalaman saya, bahkan pada VPS kelas termurah dengan RAM 512MB, performa bisa menangani lebih dari 1.000 permintaan ringan per detik dengan sedikit penyesuaian pada SQLite. Untuk memaksimalkan kinerja SQLite saat menjalankan PocketBase, mode WAL (Write-Ahead Log) harus diaktifkan. PocketBase secara default mengaktifkan mode WAL, namun jika Anda perlu menangani SQLite secara langsung, pastikan pengaturan PRAGMA journal_mode=WAL; dan PRAGMA synchronous=NORMAL; sudah aktif. Bottleneck di mana permintaan harus menunggu penulisan ke disk akan hampir hilang.


Backup Real-time dengan Cloudflare R2 dan Litestream

Kesalahan paling umum yang dilakukan indie hacker adalah menyalin file database SQLite (data.db) secara langsung dari server yang sedang berjalan untuk dijadikan backup. Metode ini berisiko tinggi menyebabkan data rusak saat backup dilakukan.

Kita menggunakan Litestream. Ini adalah alat yang mencegat frame WAL SQLite secara real-time dan mengirimkan bagian yang berubah saja ke penyimpanan cloud. Saya menyarankan Cloudflare R2 sebagai penyimpanan backup karena biaya transfer outbound gratis, sehingga biaya backup menjadi hampir nol meskipun dilakukan sesering mungkin.

Buat file konfigurasi /etc/litestream.yml seperti di bawah ini.

`yaml
dbs:

  • path: /opt/pocketbase/pb_data/data.db
    replicas:
    • type: s3
      bucket: your-r2-bucket-name
      endpoint: https://.r2.cloudflarestorage.com
      access-key-id:
      secret-access-key:

`

Dengan pengaturan ini, meskipun file db asli hilang, Anda bisa memulihkannya kembali ke kondisi 1 detik terakhir hanya dengan satu perintah.

`bash
litestream restore -if-replica-exists /opt/pocketbase/pb_data/data.db

`

Jika Anda tidak sengaja merusak data saat pengembangan dan ingin memulihkan ke titik waktu tertentu, Anda juga bisa menentukan timestamp.

`bash

1. Jeda sementara PocketBase

sudo systemctl stop pocketbase.service

2. Buat file pemulihan ke titik waktu tertentu

litestream restore -timestamp "2026-07-14T15:00:00Z" -o /tmp/recovered.db /opt/pocketbase/pb_data/data.db

3. Periksa integritas data lalu ganti file

sqlite3 /tmp/recovered.db "PRAGMA integrity_check;"
mv /tmp/recovered.db /opt/pocketbase/pb_data/data.db
chown -R pocketbase:pocketbase /opt/pocketbase/pb_data/

4. Lanjutkan layanan

sudo systemctl start pocketbase.service

`

Cloudflare R2 memberikan kapasitas penyimpanan gratis 10GB setiap bulan. Ditambah dengan jatah 1 juta permintaan tulis dan 10 juta permintaan baca gratis, bagi layanan berskala 1 orang, biaya yang keluar untuk backup mendekati Rp 0.


Deployment Tanpa Henti (Zero-Downtime) dengan GitHub Actions dan Nginx

Menjalankan deployment dengan Docker memang nyaman, namun pada VPS kecil dengan RAM 512MB atau 1GB, memori yang dimakan oleh Docker daemon itu sendiri sangat berharga. Mari implementasikan deployment tanpa henti (blue-green) dengan cara mengganti port pada systemd Linux tanpa menggunakan Docker.

Kita memanfaatkan karakteristik SQLite yang memungkinkan beberapa proses membaca dan menulis ke satu file SQLite yang sama. Kita mendaftarkan template layanan systemd agar biner PocketBase dapat dijalankan di port 9011 (biru) dan 9012 (hijau) secara terpisah.

Berikut adalah contoh alur kerja pengiriman biner ringan yang dibangun dengan GitHub Actions ke server.

`yaml

.github/workflows/deploy.yml

name: Deploy
on:
push:
branches: [ main ]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o pocketbase main.go
- name: Transfer Binary to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
source: "pocketbase"
target: "/srv/pocketbase/next_release"

`

Setelah biner tiba di server, skrip deployment akan dijalankan. Skrip ini akan memeriksa apakah port yang saat ini aktif adalah 9011 atau 9012, lalu menjalankan biner versi baru di port yang sedang tidak digunakan.

`bash
#!/bin/bash

deploy_swap.sh

CURRENT_PORT=$(curl -s http://127.0.0.1:8090/api/health | jq -r '.port' 2>/dev/null || echo "9011")

if [ "$CURRENT_PORT" = "9011" ]; then
TARGET_PORT="9012"
else
TARGET_PORT="9011"
fi

Menjalankan biner versi baru di latar belakang pada port target

sudo systemctl start pocketbase@$TARGET_PORT.service

Tunggu 3 detik untuk memastikan pengecekan kesehatan (health check)

sleep 3
HEALTH_CHECK=(curl−shttp://127.0.0.1:(curl -s http://127.0.0.1:(curl−shttp://127.0.0.1:TARGET_PORT/api/health | jq -r '.status')

if [ "HEALTH_CHECK" = "OK" ]; then # Memperbarui pengaturan upstream Nginx ke port baru lalu memuat ulang echo "upstream pocketbase { server 127.0.0.1:TARGET_PORT; }" | sudo tee /etc/nginx/conf.d/upstream.conf
sudo systemctl reload nginx

# Menghentikan proses versi sebelumnya
sudo systemctl stop pocketbase@$CURRENT_PORT.service
echo "Deployment selesai. Perpindahan ke port $TARGET_PORT berhasil."

else
echo "Deployment gagal. Versi baru gagal health check."
sudo systemctl stop pocketbase@$TARGET_PORT.service
exit 1
fi

`

Dengan menggunakan metode ini, deployment tanpa henti berjalan sempurna tanpa perlu alat orkestrasi kontainer yang mahal. Hal ini karena Nginx secara otomatis menahan permintaan untuk sementara (dalam satuan milidetik) saat dilakukan reload, kemudian dengan aman mengalihkan permintaan ke port baru.

Self-hosting memang membutuhkan usaha di awal saat pengaturan, namun setelah konfigurasi selesai, ini menjadi fondasi luar biasa yang memungkinkan Anda untuk fokus sepenuhnya pada produk tanpa mengkhawatirkan biaya infrastruktur yang dibebankan setiap bulan.