SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor

Supabase Self-Hosted Bikin VPS Crash: OOM atau Panic

VPS mati setelah menjalankan stack Supabase? Bedakan OOM kill dari kernel panic lewat jejak log yang bisa Anda baca sendiri, lalu perbaiki dengan batas memori dan swap.

Kenapa Supabase self-hosted bikin VPS crash

Supabase self-hosted yang dijalankan penuh di VPS kecil biasanya jatuh karena kehabisan memori, bukan karena ada bug di aplikasinya. Pada rilis self-hosted/v0.8.2 (dicek 23 September 2026), file docker/docker-compose.yml mendefinisikan 11 service dan tidak satu pun diberi batas memori. Artinya setiap container boleh tumbuh sampai RAM host habis, lalu kernel yang memutuskan siapa yang mati.

Masalahnya, dua kejadian yang sangat berbeda sama-sama disebut "server saya crash". Yang pertama OOM kill: OOM adalah singkatan dari out of memory, dan kernel membunuh satu proses lalu terus berjalan. SSH Anda masih hidup, tapi Postgres atau salah satu container hilang diam-diam. Yang kedua kernel panic: kernel berhenti total. SSH mati, tidak ada log baru yang tertulis, dan VPS butuh hard reboot dari panel provider. Perbaikan keduanya berbeda, jadi hal pertama yang harus Anda pastikan adalah yang mana yang benar-benar terjadi.

Kalau Anda belum sampai tahap ini, langkah pemasangannya ada di panduan memasang Supabase sendiri dengan Docker Compose. Tulisan ini melanjutkan dari titik setelah stack-nya naik dan servernya tumbang.

Apa beda OOM kill dan kernel panic?

OOM killer adalah bagian dari kernel Linux. Saat permintaan alokasi memori gagal dan tidak ada lagi halaman yang bisa dibebaskan, kernel memilih satu proses berdasarkan nilai oom_score, lalu mengirim sinyal SIGKILL ke proses itu. Proses dengan RSS (resident set size, yaitu memori fisik yang benar-benar dipakai) paling besar biasanya yang terpilih, sehingga di stack Supabase korbannya sering container database. Setelah eksekusi itu kernel kembali normal. Sistem tidak mati, ia hanya kehilangan satu proses.

Kernel panic berbeda. Kernel menemukan kondisi yang tidak bisa ia pulihkan, lalu berhenti di tempat. Tidak ada proses yang dijadwalkan lagi, sehingga tidak ada lagi yang menulis ke disk. Inilah sebabnya panic jarang meninggalkan jejak di journalctl: pesannya hanya sempat keluar ke konsol, bukan ke file log.

Kekurangan RAM murni hampir tidak pernah menyebabkan panic, karena OOM killer justru dirancang untuk mencegah hal itu. Panic yang berhubungan dengan memori muncul hanya dalam kasus ekstrem, misalnya saat kernel kehabisan memori dan tidak menemukan satu pun proses yang boleh dibunuh. Kalau VPS Anda memang panic, penyebabnya lebih sering di luar Supabase, misalnya di lapisan storage atau di host provider.

Ada kemungkinan ketiga yang paling sering disalahartikan sebagai panic. VPS masih hidup, tapi sibuk memindahkan memori ke swap sampai semuanya melambat ekstrem. SSH timeout, dashboard tidak terbuka, dan dari luar terlihat seperti mati total. Ini bukan panic dan bukan OOM kill. Ini thrashing, dan tandanya terlihat jelas di vmstat.

Langkah 1: cek konsol provider sebelum reboot

Kalau VPS tidak menjawab SSH, jangan langsung menekan tombol reboot. Buka dulu konsol VNC atau serial console di panel provider Anda. Konsol itu menampilkan apa yang keluar ke layar server, termasuk pesan yang tidak pernah sampai ke file log.

Yang Anda cari adalah baris yang diawali Kernel panic - not syncing:, biasanya diikuti alasan singkat dan sebuah call trace panjang. Kalau baris berbentuk itu ada, ini panic sungguhan, dan satu-satunya jalan keluar adalah hard reboot dari panel. Kalau yang tampil justru prompt login biasa, atau baris yang memuat Out of memory: Killed process, kernelnya masih hidup dan ini bukan panic.

Ambil screenshot konsol itu sebelum reboot. Setelah restart, isi layar tersebut hilang, dan Anda kehilangan satu-satunya bukti yang tersedia.

Langkah 2: baca kernel log di VPS Anda

Kalau server masih bisa diakses lewat SSH, kernel log adalah sumber paling langsung.

sudo dmesg -T | grep -i -E 'out of memory|oom-kill|killed process'

dmesg membaca ring buffer kernel, dan isinya hilang setiap kali server reboot. Opsi -T mengubah timestamp mentah menjadi jam yang bisa dibaca manusia. Di banyak image Ubuntu, kernel.dmesg_restrict bernilai 1, sehingga dmesg tanpa sudo gagal dengan pesan bertema Operation not permitted.

Kalau servernya sempat reboot, ring buffer sudah kosong dan Anda butuh journal:

journalctl --list-boots
sudo journalctl -k -b -1 | grep -i -E 'out of memory|oom-kill'

--list-boots menampilkan boot mana saja yang masih tersimpan. Kalau yang muncul hanya satu baris, yaitu boot 0, journald di server itu berjalan volatile. Log tidak bertahan melewati reboot, sehingga -b -1 tidak akan menemukan apa pun. Aktifkan penyimpanan permanen supaya kejadian berikutnya terekam:

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

Ada dua bentuk baris yang perlu Anda kenali. Yang pertama baris ringkasan yang memuat oom-kill: diikuti pasangan kunci seperti constraint=, oom_memcg=, dan task=. Yang kedua baris keputusan yang memuat Killed process bersama PID (process id), nama proses, dan angka anon-rss:. Nilai constraint itu penting: CONSTRAINT_NONE berarti seluruh host kehabisan RAM, sedangkan CONSTRAINT_MEMCG berarti yang habis adalah jatah satu cgroup, yaitu satu container yang sudah punya batas memori sendiri. Beda ini yang menentukan apakah Anda perlu menambah RAM host atau cukup menaikkan batas satu service.

Cara membaca kernel log lebih dalam, termasuk memisahkan pesan memori dari pesan disk dan jaringan, dibahas di catatan soal membaca kernel log di VPS.

Langkah 3: container mana yang dibunuh?

Kernel menyebut nama proses, bukan nama service. Nama postgres di kernel log bisa berarti container db, bisa juga Postgres lain yang kebetulan jalan di server yang sama. Docker menyimpan catatannya sendiri.

docker inspect --format '{{.Name}} OOMKilled={{.State.OOMKilled}} Exit={{.State.ExitCode}} Restarts={{.RestartCount}}' $(docker ps -aq)

Nilai OOMKilled=true berarti kernel membunuh container itu karena memori, dan tidak ada keraguan lagi. Tapi OOMKilled=false belum membersihkan dugaan memori. Kalau OOM terjadi di level host, bukan di dalam cgroup container, Docker sering hanya melihat proses utamanya menerima SIGKILL. Yang tersisa di sana adalah Exit=137, yaitu 128 ditambah nomor sinyal 9. Jadi bacalah Exit=137 sebagai petunjuk kuat, lalu cocokkan jamnya dengan baris Killed process di kernel log.

Untuk memantau saat kejadiannya sedang berlangsung, biarkan satu terminal terbuka:

docker events --filter event=oom --filter event=die

Perintah ini tidak mencetak riwayat lama; ia menunggu kejadian baru. Jalankan di satu sesi SSH, lalu di sesi lain naikkan stack-nya dan perhatikan apakah ada baris yang muncul.

Langkah 4: cari restart loop di status compose

Semua service di docker-compose.yml Supabase memakai restart: unless-stopped. Container yang dibunuh OOM karena itu langsung dihidupkan kembali oleh Docker. Kalau memorinya tetap kurang, ia mati lagi, lalu hidup lagi. Dari luar stack-nya terlihat "jalan", padahal ada satu service yang berputar terus.

cd supabase-project
sh run.sh status

Yang Anda baca bukan sekadar kolom status bertuliskan Up, melainkan berapa lama Up itu. Service yang sehat menunjukkan uptime yang terus bertambah sejak stack dinaikkan. Service yang sedang loop menunjukkan uptime beberapa detik saja, atau status Restarting, dan angkanya kembali kecil setiap kali Anda mengulang perintahnya. Jalankan dua kali berselang setengah menit, lalu bandingkan hasilnya.

Efeknya lebih luas dari satu container. Beberapa service memakai depends_on dengan condition: service_healthy, misalnya storage yang menunggu db dan rest. Selama satu dependensi tidak pernah mencapai status healthy, service yang menunggunya tidak akan pernah mulai, sehingga gejala yang Anda lihat justru muncul di service yang sebenarnya baik-baik saja. Logika tunggu-menunggu ini dijelaskan terpisah di cara healthcheck Docker Compose menentukan urutan start.

Log aplikasinya sering memberi konfirmasi terakhir. Postgres yang kehilangan satu proses anak menulis baris bertema was terminated by signal 9, lalu terminating any other active server processes, karena postmaster menganggap shared memory sudah tidak bisa dipercaya sehingga seluruh cluster di-restart.

sh run.sh logs db

Service Supabase mana yang aman dimatikan?

Di rilis self-hosted/v0.8.2, service intinya ada 11: studio, api-gw, auth, rest, realtime, storage, imgproxy, meta, functions, db, dan supavisor. Dua pemakan memori terbesar, yaitu analytics (Logflare) dan vector, sudah tidak ada di file utama. Keduanya pindah ke overlay opsional docker-compose.logs.yml dan hanya aktif kalau Anda menambahkannya sendiri.

Cek dulu apa yang benar-benar aktif di instalasi Anda:

sh run.sh config

Perintah itu mencetak daftar COMPOSE_FILE yang sedang dipakai. Kalau docker-compose.logs.yml ada di dalam daftar, Anda memang menjalankan Logflare dan Vector. Logflare adalah aplikasi Elixir dengan mesin virtual Erlang di belakangnya, dan di VPS 2 GB ia sering menjadi proses dengan RSS terbesar, yang artinya ia kandidat pertama OOM killer. Matikan dulu:

sh run.sh config remove logs
sh run.sh recreate

Kalau Anda meng-clone repo Supabase dari revisi lama, analytics dan vector mungkin masih duduk di dalam docker-compose.yml utama. Di situ menghapusnya berarti mengedit file, termasuk membuang blok depends_on yang menunjuk ke analytics, karena satu referensi yatim membuat docker compose menolak seluruh file. Cara overlay bekerja secara umum ada di penjelasan menggabungkan beberapa file Docker Compose.

Dokumentasi resmi Supabase menyebut realtime, storage, imgproxy, dan functions sebagai bagian yang boleh dibuang kalau memang tidak dipakai. Pertimbangannya sederhana. imgproxy hanya berguna kalau Anda memakai transformasi gambar, dan ia bergantung pada storage. functions (Edge Runtime) tidak diperlukan kalau Anda belum menulis satu pun edge function. supavisor adalah connection pooler, jadi kalau aplikasi Anda sudah punya pool sendiri dan terhubung langsung ke Postgres, ia menambah proses tanpa menambah nilai. studio adalah dashboard web, dan mematikannya menghemat memori dengan harga kehilangan antarmuka itu.

Yang jangan disentuh: db, auth, rest, meta, dan api-gw. Membuang salah satunya membuat sisa stack kehilangan sesuatu yang ia butuhkan untuk start.

Cara memasang batas memori per service

Menghapus service menurunkan pemakaian rata-rata. Batas memori mengubah akibat dari lonjakan. Tanpa batas, satu container yang melonjak bisa membuat kernel membunuh container lain yang tidak bersalah, karena OOM killer memilih berdasarkan ukuran, bukan berdasarkan siapa penyebabnya. Dengan mem_limit, container itu punya cgroup sendiri, sehingga yang mati adalah proses di dalam cgroup yang melewati batasnya.

Buat file baru di folder yang sama dengan docker-compose.yml, misalnya docker-compose.limits.yml:

services:
  db:
    mem_limit: 1g
  studio:
    mem_limit: 512m
  realtime:
    mem_limit: 512m
  storage:
    mem_limit: 256m
  auth:
    mem_limit: 256m
  rest:
    mem_limit: 256m
  meta:
    mem_limit: 256m

Lalu daftarkan file itu ke COMPOSE_FILE dan pastikan ia benar-benar terbaca:

sh run.sh config add limits
sh run.sh config
sh run.sh compose-config | grep -i -B2 'mem_limit'

Baris terakhir penting. Supabase menulis daftar file aktif ke variabel COMPOSE_FILE di .env, dan selama variabel itu diset, Docker Compose berhenti memuat docker-compose.override.yml secara otomatis. Artinya file override dengan nama bawaan itu akan diam saja tanpa satu pun pesan error. compose-config mencetak konfigurasi hasil gabungan, jadi kalau mem_limit tidak muncul di sana, ia memang tidak terpakai.

Angka di atas adalah titik awal, bukan resep untuk semua orang. Ukur dulu di server Anda sendiri:

docker stats --no-stream

Kolom pemakaian memori menunjukkan angka nyata per container. Tetapkan batas di sekitar 1,5 kali angka puncak yang Anda lihat pada beban normal. Batas yang terlalu ketat hanya memindahkan kematian dari satu container ke container lain, dan gejalanya akan berubah tanpa masalahnya selesai.

Ada satu jebakan khusus untuk db. Postgres mengalokasikan shared_buffers sekali saat start. Kalau mem_limit container lebih kecil daripada shared_buffers ditambah memori kerja per koneksi, Postgres akan mati justru karena batas yang baru Anda pasang. Periksa nilainya sebelum memilih angka:

docker compose exec db psql -U postgres -c 'SHOW shared_buffers;'

Ganti postgres dengan nilai POSTGRES_USER di .env Anda kalau perintah itu ditolak. Pembahasan lengkap soal cara batas ini bekerja, termasuk beda mem_limit dan deploy.resources.limits.memory, ada di panduan batas memori per service di Docker Compose.

Apakah menambah swap menyelesaikan masalahnya?

Banyak panduan menyarankan menambah swap lalu menganggap perkaranya selesai. Swap memang berguna, tapi perannya terbatas. Ia memindahkan halaman memori yang jarang dipakai ke disk, sehingga lonjakan pendek tidak langsung memicu OOM killer. Ia tidak membuat server punya RAM lebih banyak.

Cek dulu apakah swap sudah ada:

swapon --show
free -h

Kalau swapon --show tidak mencetak apa pun, berarti tidak ada swap aktif. Menambahkannya:

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Baris fstab itulah yang membuat swap kembali setelah reboot. Tanpa baris tersebut, swap hilang pada boot berikutnya dan servernya rentan lagi tanpa Anda sadari. Di filesystem btrfs atau zfs, fallocate menghasilkan file yang ditolak swapon dengan pesan bertema invalid argument, dan gantinya adalah dd if=/dev/zero of=/swapfile bs=1M count=4096.

Sekarang bagian yang jarang ditulis. Di banyak provider, swap berarti menulis ke storage jaringan, yang latensinya jauh lebih tinggi daripada NVMe lokal. Saat Postgres mulai membaca halaman yang sudah terlempar ke swap, server bisa melambat sampai SSH pun timeout. Inilah kondisi thrashing yang tadi disebut, dan biasanya inilah yang orang laporkan sebagai "VPS saya panic". Buktikan sendiri:

vmstat 2 10

Kolom si dan so menghitung berapa banyak halaman yang masuk dan keluar swap per detik. Angka yang terus tinggi di kedua kolom itu berarti server menghabiskan waktunya memindahkan memori, bukan mengerjakan query Anda.

vm.swappiness mengatur seberapa agresif kernel memakai swap. Nilai bawaan 60 terlalu agresif untuk server database. Turunkan supaya swap menjadi cadangan:

echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system

Swap juga memakan ruang disk, padahal stack Supabase sudah menulis banyak ke volume db. Kalau setelah menambah swapfile partisi root Anda mulai penuh, cara menelusurinya ada di tulisan soal angka df dan du yang tidak cocok.

Berapa RAM yang dibutuhkan Supabase self-hosted?

Dokumentasi self-hosting Supabase mencantumkan kebutuhan perangkat kerasnya sendiri. Angka di bawah ini diambil dari halaman resmi tersebut pada 23 September 2026.

ChartKebutuhan perangkat keras Supabase self-hosted menurut dokumentasi resmi, dicek 23 September 2026
The data behind this chart
[
  {
    "label": "Minimum",
    "ram_gb": 4,
    "cpu_core": 2,
    "disk_gb": 40
  },
  {
    "label": "Rekomendasi",
    "ram_gb": 8,
    "cpu_core": 4,
    "disk_gb": 80
  }
]

Supabase menyebut 4 GB RAM dan 2 core sebagai minimum untuk menjalankan seluruh komponennya, lalu menaikkannya ke 8 GB RAM, 4 core, dan 80 GB SSD sebagai rekomendasi. Tanda plus pada angka rekomendasi ada di dokumentasi aslinya, yang artinya itu lantai dan bukan target.

Terjemahan praktisnya begini. VPS 1 GB tidak cukup untuk stack ini dalam bentuk apa pun, dan tidak ada konfigurasi yang menyelamatkannya. VPS 2 GB bisa hidup kalau Anda memangkas service opsional dan memasang batas memori, tapi tidak menyisakan ruang untuk lonjakan. Kalau ini untuk produksi, mulailah dari 4 GB dan perlakukan 8 GB sebagai sasaran yang wajar.

Momen paling rawan bukan saat server idle, melainkan beberapa menit pertama setelah sh run.sh start. Semua container start bersamaan, dan Postgres menjalankan migrasi awal di saat yang sama. Kalau server Anda mati, kemungkinan besar di sanalah. Ukur, jangan menebak: jalankan docker stats --no-stream beberapa kali selama dua menit pertama, lalu catat angka tertingginya. Cara memetakan hasil ukuran itu ke paket VPS yang tepat dibahas di panduan memilih ukuran VPS untuk beban kerja umum.

Urutan perbaikan yang saya sarankan

  1. Pastikan dulu jenis kegagalannya lewat konsol provider dan kernel log, karena panic dan OOM kill butuh penanganan yang berbeda.
  2. Buang analytics dan vector kalau overlay logs aktif. Ini penghematan terbesar yang bisa dicapai dengan satu perintah.
  3. Matikan service opsional yang memang tidak Anda pakai.
  4. Tambahkan swap secukupnya dengan vm.swappiness rendah, sebagai peredam untuk lonjakan pendek.
  5. Pasang mem_limit per service supaya lonjakan satu container tidak lagi membunuh container lain.
  6. Kalau setelah semua itu server masih mati, ukurannya memang terlalu kecil. Tambah RAM.

Setelah setiap perubahan, naikkan ulang stack-nya dan periksa lagi dengan sh run.sh status, jangan menumpuk beberapa perubahan sekaligus. Kalau Anda ragu apakah container perlu di-restart atau dibangun ulang dari image baru, bedanya dijelaskan di catatan soal restart versus rebuild di Docker Compose.

FAQ

Apa beda OOM kill dan kernel panic di VPS?

Pada OOM kill, kernel membunuh satu proses lalu terus berjalan. Server tetap bisa di-SSH, dan jejaknya berupa baris yang memuat Killed process di dmesg atau journalctl -k. Pada kernel panic, kernel berhenti total sehingga tidak ada lagi yang menulis log. Bukti satu-satunya adalah baris Kernel panic - not syncing: di konsol VNC atau serial console milik provider, dan server perlu hard reboot dari panel. Kekurangan RAM hampir selalu menghasilkan yang pertama, bukan yang kedua.

Kenapa container Supabase saya hilang tapi servernya tetap hidup?

Itu tanda khas OOM kill. Kernel memilih proses dengan RSS terbesar, dan di stack Supabase biasanya itu container database atau Logflare. Jalankan docker inspect --format '{{.Name}} {{.State.OOMKilled}} {{.State.ExitCode}}' $(docker ps -aq) dan cari nilai true atau kode keluar 137, yaitu 128 ditambah sinyal 9. Karena semua service memakai restart: unless-stopped, container itu langsung hidup lagi, jadi periksa juga berapa lama uptime-nya lewat sh run.sh status.

Berapa RAM minimal untuk Supabase self-hosted?

Dokumentasi resmi Supabase menyebut 4 GB RAM dengan 2 core sebagai minimum, dan 8 GB RAM dengan 4 core sebagai rekomendasi, dicek pada 23 September 2026. VPS 1 GB tidak cukup untuk rilis self-hosted/v0.8.2 yang berisi 11 service. VPS 2 GB bisa dipakai untuk mencoba kalau service opsional dimatikan dan setiap service diberi mem_limit, tapi tidak menyisakan ruang aman untuk beban produksi.

Apakah menambah swap cukup untuk mengatasi masalah ini?

Tidak cukup sendirian. Swap menunda OOM killer dengan memindahkan halaman yang jarang dipakai ke disk, sehingga lonjakan pendek bisa dilewati. Tapi kalau kekurangan memorinya berlangsung terus, server masuk kondisi thrashing: waktunya habis untuk memindahkan halaman, dan SSH bisa ikut timeout. Pantau kolom si dan so pada vmstat 2 10. Angka yang terus tinggi di kedua kolom itu berarti swap sudah menjadi masalah baru, bukan solusi.

Service Supabase mana yang aman dimatikan di VPS kecil?

Pada rilis self-hosted/v0.8.2, analytics dan vector berada di overlay opsional docker-compose.logs.yml dan bisa dilepas dengan sh run.sh config remove logs. Dokumentasi resmi juga menyebut realtime, storage, imgproxy, dan functions boleh dibuang kalau tidak dipakai. supavisor hanya perlu kalau Anda memakai connection pooler, dan studio hanya menyediakan dashboard web. Yang wajib tetap hidup adalah db, auth, rest, meta, dan api-gw.