Cara Self-Host Supabase di VPS dengan Docker
Jalankan stack Docker Supabase resmi di server sendiri. Panduan ini membahas secret demo yang wajib diganti, fungsi 14 service, kebutuhan RAM, backup, dan update.
Yang Anda bangun
Self-hosting Supabase berarti menjalankan stack Docker Compose resmi pada server Anda sendiri: Postgres, REST API di depannya, service autentikasi, penyimpanan file, websocket realtime, dan dashboard Studio. Anda meng-clone satu repository, mengedit satu file .env, lalu menjalankan sekitar empat belas container yang bersama-sama berfungsi seperti project Supabase yang Anda kendalikan.
Instalasinya singkat. Masalah biasanya muncul pada file .env. File tersebut berisi secret demo yang dipublikasikan di repository, dan stack yang dijalankan dengan default tersebut dapat diakses siapa pun yang menemukannya. Panduan ini membahas secret yang harus Anda ganti, fungsi setiap service, kebutuhan memori stack yang sebenarnya, serta cara memperbaruinya tanpa menghapus database.
Jika Compose masih baru bagi Anda, baca Dasar-dasar Docker Compose di VPS terlebih dahulu. Semua langkah di bawah mengasumsikan docker compose version sudah menampilkan versi.
Isi sebenarnya dari stack
Supabase bukan satu program. File Compose memulai sekumpulan service terpisah dalam satu jaringan. Dengan mengetahui fungsi setiap service, Anda dapat mengubah daftar nama container menjadi sesuatu yang dapat di-debug.
dbadalah PostgreSQL dengan ekstensi Supabase yang dimuat. Semua service lain berkomunikasi dengannya. Jika container ini tidak sehat, service lainnya juga gagal.kongadalah gateway API. Service ini listen pada port 8000 dan merutekan/rest/v1/,/auth/v1/, serta/storage/v1/ke backend yang sesuai. Hanya container inilah yang boleh Anda expose.restadalah PostgREST. Service ini membaca schema Postgres dan menyediakannya sebagai REST API. Dengan demikian, tabel baru menjadi endpoint baru tanpa kode tambahan.authadalah GoTrue. Service ini menerbitkan JSON web token (JWT) yang mengidentifikasi pengguna Anda.storagedanimgproxymenangani upload file dan pengubahan ukuran gambar.realtimemen-stream perubahan database melalui websocket.studiodanmetaadalah dashboard serta admin API yang mendukungnya.analytics(Logflare) danvectormengumpulkan log, sedangkansupavisoradalah pooler koneksi Postgres.
Daftar tersebut menjelaskan alasan angka resource di bagian berikutnya. Anda tidak hanya menjalankan database. Anda menjalankan database beserta sekitar selusin service pendukung.
Penentuan kapasitas: rencanakan 8 GB RAM
Stack menggunakan sekitar 2.5 hingga 3 GB memori resident saat idle pada instalasi baru, per Juli 2026, sebelum data atau trafik Anda ditambahkan. Layanan analytics dan proses Studio Node.js merupakan dua konsumen tunggal terbesar. Server dengan RAM 2 GB akan menjalankan container, lalu salah satunya dihentikan oleh kernel melalui out-of-memory killer, biasanya analytics atau db. Gejalanya adalah container terus-menerus melakukan restart dengan exit code 137.
Gunakan RAM 8 GB dan 4 vCPU untuk layanan apa pun yang Anda andalkan. RAM 4 GB cukup untuk instance pengembangan pribadi jika Anda menerima bahwa query berat dan sesi Studio yang berjalan bersamaan akan lambat. Kapasitas disk juga penting karena Postgres, volume penyimpanan, dan data log semuanya berada di bawah direktori project. Mulailah dengan 40 GB, lalu pantau penggunaannya. Menghitung kebutuhan layanan sebelum memilih plan adalah kebiasaan yang perlu dipertahankan untuk semua layanan yang Anda self-host, karena PhotoPrism dan Immich memiliki kebutuhan RAM minimum yang jauh lebih tinggi daripada yang tersirat pada halaman quick start-nya.
Instalasi: clone repositori resmi
Metode yang didukung menyalin direktori docker dari repositori utama ke direktori proyek Anda sendiri. Pemisahan ini penting karena git pull berikutnya tidak akan menimpa .env Anda.
git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pulldocker compose pull mengunduh beberapa gigabyte image. Proses ini harus berakhir dengan setiap service berstatus Pulled. Error manifest unknown di sini berarti tag image yang ditetapkan telah dihapus di upstream. Solusinya adalah mengambil salinan repositori yang lebih baru, bukan mengubah tag secara manual.
Rahasia yang harus Anda ubah sebelum start pertama
Lakukan ini sebelum Anda menyalakan stack, bukan setelahnya. Beberapa nilai ini ditulis ke dalam data saat boot pertama, sehingga mengubahnya nanti berarti mereset database.
Repository ini menyediakan generator yang menghasilkan semua nilai dengan benar, termasuk dua API key yang harus ditandatangani menggunakan JWT secret baru Anda.
sh utils/generate-keys.sh --update-envScript tersebut menulis nilai baru untuk JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY, dan token Logflare ke dalam .env. Script ini memerlukan openssl, yang tersedia pada image Ubuntu normal.
Ada dua nilai yang tidak diatur oleh script tersebut dan harus Anda edit secara manual di .env:
POSTGRES_PASSWORD. Gunakan hanya huruf dan angka. Tanda baca di sini merusak connection string yang dibuat oleh beberapa service dengan menggabungkan string. Kegagalan ini terlihat seperti error autentikasi, bukan error parsing, sehingga Anda dapat mencari penyebab di tempat yang salah.DASHBOARD_USERNAMEdanDASHBOARD_PASSWORD. Keduanya adalah kredensial autentikasi dasar untuk Studio. Password default yang disertakan secara harfiah adalahthis_password_is_insecure_and_should_be_updated.
Pahami alasan ANON_KEY dan SERVICE_ROLE_KEY tidak boleh dibuat sembarangan. Keduanya adalah JWT yang ditandatangani dengan JWT_SECRET. Gateway memverifikasi tanda tangan tersebut pada setiap request, sehingga key yang tidak cocok dengan secret Anda akan ditolak dengan {"message":"Invalid authentication credentials"}. Ini adalah kegagalan self-hosting yang paling umum: operator mengubah JWT_SECRET, tetapi tetap menggunakan key demo. Selalu generate ketiganya secara bersamaan.
Perlakukan SERVICE_ROLE_KEY seperti password root. Nilai ini sepenuhnya melewati row level security. Nilai ini hanya boleh digunakan dalam kode sisi server.
Atur SITE_URL dan API_EXTERNAL_URL ke alamat yang benar-benar akan diakses pengguna, misalnya https://supabase.example.com. Auth membuat link konfirmasi email dan callback OAuth dari nilai tersebut. Jika tetap menggunakan http://localhost:8000, semua pengguna Anda akan diarahkan ke mesin mereka sendiri.
Kemudian periksa nilai yang telah Anda atur:
sh run.sh secretsJalankan dan pastikan statusnya sehat
sh run.sh start
docker compose psrun.sh start membungkus docker compose up -d --wait, sehingga perintah tersebut tidak selesai sampai pemeriksaan kesehatan berhasil. Setiap service harus menampilkan running (healthy) atau running. Boot pertama memerlukan waktu dua hingga empat menit karena Postgres menjalankan skrip inisialisasinya sebelum komponen lain dapat terhubung.
Jika sebuah container terus melakukan restart, baca log-nya berdasarkan nama service:
docker compose logs db
docker compose logs authStudio tersedia pada port 8000, lalu akan meminta username dan password dashboard yang Anda tetapkan.
Jangan mengekspos port 8000 ke Internet publik
Kong pada port 8000 menggunakan HTTP biasa. Setiap kunci API dan kata sandi pengguna melintasi jaringan dalam bentuk teks biasa. Kredensial Studio menggunakan autentikasi dasar, yaitu pengodean base64, bukan enkripsi.
Tempatkan reverse proxy di depannya, lakukan terminasi TLS (keamanan lapisan transport) di sana, lalu bind Kong ke alamat loopback agar tidak ada layanan lain yang dapat mengaksesnya. Dalam docker-compose.yml, pemetaan port kong menjadi 127.0.0.1:8000:8000, lalu proxy meneruskan permintaan ke port tersebut. Traefik di depan beberapa aplikasi Compose membahas konfigurasi sertifikat.
Tutup port lainnya di firewall juga. Docker memublikasikan port dengan menulis aturan iptables sendiri, yang tidak terlihat oleh konfigurasi ufw sederhana. Jebakan ini dijelaskan dalam alasan container Docker mengabaikan aturan ufw Anda.
Cadangkan database, bukan direktori
Data Postgres berada dalam bind mount di ./volumes/db/data. Menyalin direktori tersebut saat container berjalan menghasilkan salinan yang tidak konsisten karena Postgres men-buffer penulisan, dan file di disk hanya konsisten saat checkpoint. Pemulihan biasanya tetap berhasil, tetapi terkadang transaksi terakhir hilang tanpa pesan. Ini adalah mode kegagalan terburuk untuk sebuah backup.
Gunakan dump. pg_dumpall berjalan di dalam container dan menghasilkan snapshot yang konsisten:
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sqlPastikan file tidak kosong sebelum mengandalkannya. Kemudian kirim dump tersebut ke luar server sesuai jadwal. Untuk itu, gunakan backup terenkripsi di lokasi lain dengan restic. Cadangkan .env pada waktu yang sama. Jika JWT_SECRET hilang, semua token yang telah diterbitkan menjadi tidak valid dan semua secret terenkripsi yang tersimpan tidak dapat dibaca.
File yang diunggah berada di ./volumes/storage. File tersebut adalah file biasa, sehingga dapat disalin dengan metode biasa.
Perbarui tanpa kehilangan data
Supabase menetapkan versi image di docker-compose.yml, sehingga tidak ada perubahan sampai Anda mengubahnya. Praktik pinning ini layak diterapkan pada setiap stack yang Anda susun secara manual. Karena itu, relay RustDesk yang di-host sendiri menetapkan dua image servernya, bukan mengikuti tag yang terus berubah. Upgrade harus dilakukan pada waktu yang Anda pilih dan saat Anda memiliki waktu untuk menanganinya. Selalu buat dump terlebih dahulu.
docker compose pull
sh run.sh recreaterecreate menghentikan stack lalu menjalankannya kembali dengan image baru. Data Anda tetap ada karena disimpan dalam bind mount pada host, bukan di dalam container. Baca CHANGELOG.md di repository sebelum melakukan lompatan versi mayor, karena upgrade mayor Postgres tidak berjalan otomatis dan memerlukan dump serta restore.
Untuk menerapkan perubahan pada file Compose itu sendiri, clone kembali repository upstream lalu salin direktori docker ke project Anda. Pastikan .env tidak tertimpa.
Reset penuh, yang menghapus semuanya termasuk database, menggunakan script terpisah dan meminta konfirmasi:
sh reset.shFAQ
Mengapa panggilan API saya mengembalikan "Invalid authentication credentials"?
ANON_KEY atau SERVICE_ROLE_KEY Anda tidak ditandatangani menggunakan JWT_SECRET yang saat ini berada di .env. Gateway memverifikasi signature pada setiap request dan menolak ketidaksesuaian. Buat ulang ketiganya secara bersamaan dengan sh utils/generate-keys.sh --update-env, lalu jalankan sh run.sh recreate agar service membaca nilai baru.
Apakah saya dapat menjalankan Supabase yang di-host sendiri pada VPS 2 GB?
Tidak secara andal. Stack ini menggunakan hampir 3 GB saat idle per Juli 2026 karena menjalankan sekitar empat belas service. Akibatnya, server 2 GB akan kehilangan container karena out of memory killer dan Anda akan melihat exit code 137 di docker compose ps. Gunakan 8 GB untuk production dan anggap 4 GB sebagai batas minimum untuk pengembangan pribadi.
Apakah Supabase yang di-host sendiri menyertakan edge functions?
Ya. File Compose menyertakan runtime functions berbasis Deno dan menyajikan apa pun yang Anda tempatkan di bawah ./volumes/functions. Runtime ini tidak menyertakan jaringan deployment global milik platform hosted. Karena itu, functions Anda berjalan pada satu server dan satu lokasi.
Bagaimana cara terhubung langsung ke database Postgres?
Gunakan docker exec -it supabase-db psql -U postgres untuk membuka shell interaktif langsung pada server. Untuk client eksternal, terhubung melalui Supavisor pada port 5432 menggunakan user postgres.<POOLER_TENANT_ID> dan POSTGRES_PASSWORD Anda. Jangan membuka port tersebut ke Internet. Akses melalui VPN atau tunnel SSH.
Mengapa email konfirmasi auth saya mengarah ke localhost?
SITE_URL dan API_EXTERNAL_URL di .env masih menggunakan nilai default. auth service membuat setiap link konfirmasi dan reset password berdasarkan kedua nilai tersebut. Karena itu, service mengirimkan alamat yang dikonfigurasikan untuk dikirim. Tetapkan keduanya ke URL publik yang sebenarnya, lalu buat ulang stack.