SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-10-11

Cài Supabase self-host trên VPS bằng Docker

Chạy stack Docker Supabase chính thức trên VPS: thay secret demo trong .env, hiểu 11 service, tính RAM, backup database và update an toàn.

Bạn đang xây dựng gì

Self-hosting Supabase nghĩa là chạy stack Docker Compose chính thức trên server của bạn: Postgres, REST API phía trước Postgres, auth service, file storage, realtime websocket và dashboard Studio. Bạn clone một repository, chỉnh sửa một file .env rồi khởi động 11 container. Các container này cùng hoạt động như một Supabase project do bạn quản lý.

Quá trình cài đặt khá ngắn. Vấn đề thường xảy ra nằm ở file .env. File này chứa sẵn các secret demo đã được công khai trong repository. Stack khởi động với các giá trị mặc định đó sẽ cho phép bất kỳ ai tìm thấy nó truy cập. Hướng dẫn này nêu rõ các secret bạn phải thay thế, chức năng của từng service, dung lượng memory thực tế mà stack cần và cách update stack mà không xóa database.

Nếu Compose còn mới với bạn, hãy đọc Kiến thức cơ bản về Docker Compose trên VPS trước. Các bước bên dưới giả định lệnh docker compose version đã in ra version.

Thực tế stack gồm những gì

Supabase không phải là một chương trình duy nhất. File Compose khởi động một nhóm service riêng biệt trên cùng một network. Biết vai trò của từng service giúp bạn biến một danh sách dài tên container thành hệ thống có thể debug.

  • db là PostgreSQL đã load các extension của Supabase. Mọi service khác đều kết nối đến nó. Nếu container này không healthy, các service còn lại cũng sẽ fail.
  • api-gw là API gateway. Từ bản release tháng 2026-08, nó chạy Envoy. Trước đó, đây là service có tên kong. Kong vẫn có thể dùng thông qua override tùy chọn. Service này listen trên port 8000 và route /rest/v1/, /auth/v1/ và /storage/v1/ đến đúng backend. Đây là container duy nhất bạn nên expose.
  • rest là PostgREST. Nó đọc schema Postgres của bạn và cung cấp schema đó dưới dạng REST API. Vì vậy, thêm một table sẽ tạo một endpoint mới mà không cần viết code.
  • auth là GoTrue. Nó phát hành JSON web token (JWT) để định danh user.
  • storage và imgproxy xử lý việc upload file và resize image.
  • realtime stream các thay đổi trong database qua websocket.
  • studio và meta là dashboard và admin API đứng sau dashboard.
  • functions là edge runtime dựa trên Deno, còn supavisor là connection pooler cho Postgres.

Log và analytics (Logflare cùng vector) đã bị loại khỏi file Compose mặc định vào tháng 2026-06. Hiện chúng nằm trong một file override tùy chọn. Bạn bật chúng bằng sh run.sh config add logs, theo cùng cách layer nhiều file Compose được mô tả trong chạy một stack từ nhiều file Compose.

Danh sách này giải thích các chỉ số tài nguyên bên dưới. Bạn không chỉ chạy một database. Bạn đang chạy một database cùng với 10 service hỗ trợ.

Cấu hình tài nguyên: chuẩn bị 8 GB RAM

Stack sử dụng khoảng 2.5 đến 3 GB resident memory khi idle trên một cài đặt mới vào tháng 7 năm 2026, chưa tính dữ liệu hoặc traffic của bạn. Trong phép đo đó, analytics service và process Studio Node.js là 2 thành phần tiêu thụ nhiều memory riêng lẻ nhất. Analytics không còn thuộc stack mặc định, nên chỉ tiêu tốn memory nếu bạn bật logs override. Server 2 GB sẽ khởi động các container rồi kernel out of memory killer sẽ dừng một container. Triệu chứng là container liên tục restart với exit code 137. Tài liệu self-hosting của Supabase, được kiểm tra vào tháng 10 năm 2026, liệt kê 4 GB RAM, 2 CPU core và 40 GB SSD là mức tối thiểu; mức khuyến nghị là 8 GB RAM, 4 core và 80 GB.

Hãy cấp 8 GB RAM và 4 vCPU cho mọi hệ thống mà bạn phụ thuộc vào. 4 GB vẫn đủ cho một instance development cá nhân nếu bạn chấp nhận việc một query nặng chạy cùng lúc với một session Studio sẽ chậm. Disk cũng quan trọng vì Postgres, storage volume và dữ liệu log nếu bạn bật logs đều nằm trong project directory. Bắt đầu với 40 GB rồi theo dõi mức sử dụng. Việc đếm số service trước khi chọn plan là thói quen nên duy trì với mọi phần mềm bạn self-host, vì PhotoPrism và Immich cũng có mức RAM tối thiểu thực tế: trang quick start của Immich, được kiểm tra vào tháng 10 năm 2026, yêu cầu ít nhất 6 GB RAM và 2 CPU core. Nếu hiện tại dự án của bạn chưa cần đến một server như vậy, hãy so sánh với những gì gói cloud miễn phí của Supabase thực sự cung cấp, vì một app nhỏ luôn dưới 500 MB và không idle suốt một tuần có thể chạy trên đó mà không mất phí.

Cài đặt: clone repository chính thức

Cách được hỗ trợ là sao chép thư mục docker từ repository chính vào một thư mục dự án riêng của bạn. Việc tách riêng này rất quan trọng, vì sau đó một lần git pull sẽ không thể ghi đè lên .env của bạn.

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 pull

docker compose pull tải xuống vài gigabyte image. Lệnh này phải kết thúc với mọi service được đánh dấu Pulled. Lỗi manifest unknown ở đây có nghĩa là upstream đã xóa tag image được ghim. Cách khắc phục là pull bản repository mới hơn, không phải tự sửa tag.

Các secret phải đổi trước lần khởi động đầu tiên

Thực hiện việc này trước khi khởi động stack, không phải sau đó. Một số giá trị được ghi vào dữ liệu ngay trong lần boot đầu tiên, nên đổi chúng sau đó sẽ yêu cầu reset database.

Repository có sẵn một generator để tạo đúng mọi giá trị, bao gồm 2 API key phải được ký bằng JWT secret mới của bạn.

sh utils/generate-keys.sh --update-env

Script đó ghi các giá trị mới cho JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY, các token Logflare, các key của giao thức S3 và MINIO_ROOT_PASSWORD vào .env. Tính đến tháng 10 năm 2026, script cũng ghi các giá trị ngẫu nhiên cho POSTGRES_PASSWORD và DASHBOARD_PASSWORD. Script cần openssl, thành phần này có trên mọi Ubuntu image thông thường. Vì hiện tại script cũng ghi đè database password, chỉ chạy nó với --update-env trước lần khởi động đầu tiên. Không chạy trên stack đã có dữ liệu.

Có 2 giá trị vẫn nên kiểm tra thủ công trong .env:

  • POSTGRES_PASSWORD. Script điền giá trị hex ngẫu nhiên, cách này an toàn. Nếu thay bằng giá trị riêng, chỉ dùng chữ cái và chữ số. Dấu câu ở đây làm hỏng các connection string mà một số service tạo bằng cách nối chuỗi. Lỗi khi đó trông giống lỗi authentication hơn là lỗi parsing, khiến bạn điều tra sai hướng.
  • DASHBOARD_USERNAME và DASHBOARD_PASSWORD. Đây là thông tin xác thực basic authentication cho Studio. Script đặt password ngẫu nhiên nhưng giữ username mặc định là supabase. Password mặc định được cung cấp là chính xác this_password_is_insecure_and_should_be_updated, nên nếu bạn đã bỏ qua script thì hãy đổi nó ngay.

Cần hiểu vì sao không thể tự tạo ANON_KEY và SERVICE_ROLE_KEY. Cả 2 đều là JWT được ký bằng JWT_SECRET. Gateway chỉ chấp nhận đúng các key trong .env, còn các service phía sau xác minh chữ ký bằng JWT_SECRET, nên key không khớp sẽ bị từ chối với HTTP 401. Envoy gateway mặc định trả về 401 Unauthorized. Kong override hiện tại cũng trả về giá trị này; nó chạy Kong 3.9 và trả về {"message":"Unauthorized"}. Các bản cài đặt trước tháng 3 năm 2026 chạy Kong 2.8 và trả về {"message":"Invalid authentication credentials"}. Đây là lỗi self-hosting phổ biến nhất: operator đổi JWT_SECRET nhưng vẫn giữ các key demo. Luôn tạo cả 3 giá trị cùng nhau.

Hãy coi SERVICE_ROLE_KEY như root password. Nó bỏ qua hoàn toàn row level security. Chỉ đặt giá trị này trong server-side code, không đặt ở nơi nào khác.

Đặt SITE_URL thành địa chỉ của app, và API_EXTERNAL_URL thành địa chỉ public của API kèm /auth/v1 ở cuối, ví dụ https://supabase.example.com/auth/v1. Đặt SUPABASE_PUBLIC_URL thành cùng địa chỉ public đó nhưng không có path. Auth tạo link xác nhận email và callback link OAuth từ các giá trị này. Nếu giữ nguyên các giá trị mặc định localhost, mọi user sẽ bị chuyển về máy của chính họ.

Sau đó kiểm tra các giá trị:

sh run.sh secrets

Khởi động và xác nhận service hoạt động bình thường

sh run.sh start
docker compose ps

run.sh start bao bọc docker compose up -d --wait, nên lệnh sẽ không trả về cho đến khi các health check đạt yêu cầu. Mỗi service phải hiển thị running (healthy) hoặc running. Lần khởi động đầu tiên mất từ hai đến bốn phút vì Postgres chạy các script khởi tạo trước khi bất kỳ thành phần nào khác có thể kết nối.

Nếu container liên tục restart, hãy đọc log theo tên service:

docker compose logs db
docker compose logs auth

Studio sẽ chạy trên cổng 8000 và yêu cầu username cùng password của dashboard mà bạn đã đặt.

Không đưa cổng 8000 ra Internet công cộng

Gateway trên cổng 8000 sử dụng HTTP thuần. Mọi API key và mật khẩu người dùng đều đi qua mạng dưới dạng văn bản rõ, còn thông tin xác thực của Studio là basic authentication, tức chỉ được mã hóa bằng base64 chứ không phải mã hóa bảo mật.

Đặt một reverse proxy phía trước gateway, thực hiện TLS termination (transport layer security) tại đó, rồi bind gateway vào địa chỉ loopback để không thành phần nào khác có thể truy cập. Trong docker-compose.yml, dòng port bên dưới service api-gw sẽ thành 127.0.0.1:${API_GW_HTTP_PORT:-${KONG_HTTP_PORT:-8000}}:8000/tcp, rồi proxy chuyển tiếp đến cổng đó. Nếu cài đặt từ trước thời điểm chuyển sang Envoy vào tháng 8 năm 2026, dòng port tương tự nằm bên dưới service có tên kong. Traefik phía trước nhiều ứng dụng Compose trình bày phần chứng chỉ. Reverse proxy đó cũng sẽ đứng trước mọi dịch vụ khác trên máy, từ stack này đến một thứ không nghiêm túc như thư viện Jellyfin được dựng lại như một cửa hàng băng video thập niên 90, và mỗi dịch vụ đều nên dùng hostname thay vì mở thêm một cổng. Nếu một dashboard chỉ dành cho bạn, hãy bỏ qua proxy và truy cập cổng loopback qua SSH tunnel. Đây cũng là cách open-kritt tự host dùng để giữ hoàn toàn giao diện quét ngoài Internet công cộng.

Đồng thời đóng các cổng còn lại trên firewall, vì Docker tạo các rule iptables riêng khi publish port mà cấu hình ufw đơn giản thường không nhìn thấy. Vấn đề này được giải thích trong vì sao container Docker bỏ qua rule ufw của bạn.

Sao lưu database, không sao lưu thư mục

Dữ liệu Postgres nằm trong bind mount tại ./volumes/db/data. Sao chép thư mục đó khi container đang chạy sẽ tạo ra bản sao không nhất quán, vì Postgres buffer các lần ghi và các file trên disk chỉ nhất quán tại checkpoint. Khôi phục từ bản sao này thường vẫn hoạt động, nhưng đôi khi sẽ âm thầm làm mất các transaction cuối cùng. Đây là kiểu lỗi tệ nhất đối với một bản backup.

Thay vào đó, hãy dump database. pg_dumpall chạy bên trong container và tạo snapshot nhất quán:

docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sql

Kiểm tra để chắc chắn file không rỗng trước khi tin dùng nó. Sau đó định kỳ chuyển các bản dump đó ra khỏi server. Đây là mục đích của backup mã hóa ngoài site bằng restic. Một bản dump được lập lịch nhưng âm thầm thất bại cũng không khác gì không có backup, vì vậy hãy cấu hình cron hoặc systemd job gửi cảnh báo đến điện thoại khi tiến trình kết thúc với mã khác 0. Đồng thời sao lưu .env. Nếu mất JWT_SECRET, mọi token đã cấp sẽ mất hiệu lực và mọi secret được lưu dưới dạng mã hóa sẽ không thể đọc được.

Các file đã upload nằm trong ./volumes/storage. Đây là các file thông thường nên có thể sao chép trực tiếp.

Cập nhật mà không mất dữ liệu

Supabase ghim phiên bản image trong docker-compose.yml, nên không có gì thay đổi cho đến khi bạn chủ động cập nhật. Cách ghim phiên bản này cũng nên được áp dụng cho mọi stack bạn tự lắp ráp. Vì vậy, một relay RustDesk tự host ghim 2 image server thay vì theo một tag luôn thay đổi: nâng cấp phải là việc bạn chủ động chọn vào một buổi sáng có đủ thời gian. Lần nào cũng hãy tạo dump trước.

docker compose pull
sh run.sh recreate

recreate dừng stack rồi khởi động lại bằng các image mới. Dữ liệu vẫn còn vì nó nằm trong bind mount trên host, không nằm bên trong container. Trước khi nâng cấp major version, hãy đọc CHANGELOG.md trong repository, vì nâng cấp major của Postgres là một bước riêng mà recreate không thực hiện. Postgres 17 là phiên bản mặc định từ tháng 6 năm 2026. Một hệ thống hiện có trên Postgres 15 sẽ chuyển lên phiên bản này bằng sudo bash utils/upgrade-pg17.sh. Lệnh này dùng pg_upgrade để migrate dữ liệu tại chỗ và yêu cầu xác nhận ở mỗi bước major. Tất cả container phải đang chạy. Disk trống cũng phải có ít nhất gấp đôi dung lượng database cộng thêm 5 GB. Supabase vẫn khuyến nghị bạn tạo một bản backup độc lập trước khi bắt đầu.

Để áp dụng các thay đổi trong file Compose, hãy chạy sh update.sh từ thư mục project, hoặc chạy sh update.sh --dry-run để xem trước các thay đổi. Lệnh này gộp các file upstream với các chỉnh sửa của bạn. Nó không ghi đè các giá trị hiện có trong .env mà chỉ append các key mới. Trước khi ghi, lệnh này lưu cấu hình vào backups/, nhưng không lưu database. Một bản cài đặt không có file .supabase-version cần --from và release mà nó được khởi tạo từ đó. Điều này đặc biệt quan trọng với release tháng 8 năm 2026. Release này thay service kong bằng api-gw và yêu cầu cập nhật docker-compose.yml. Bạn vẫn có thể thực hiện thủ công: clone lại upstream repository rồi copy thư mục docker vào project, nhưng phải cẩn thận để không ghi đè .env.

Full reset sẽ xóa mọi thứ, bao gồm database, và đưa .env về các giá trị mặc định. Bản cũ được giữ lại dưới dạng .env.old. Đây là một script riêng và script sẽ yêu cầu xác nhận:

sh reset.sh

FAQ

Vì sao các API call của tôi trả về “Thông tin xác thực không hợp lệ”?

Thông báo chính xác này đến từ Kong 2.8, gateway trong stack ở các bản cài đặt trước tháng 3 năm 2026. Envoy gateway mặc định và cấu hình Kong hiện tại, chạy Kong 3.9, báo cùng vấn đề dưới dạng 401 thuần Unauthorized. Dù theo cách nào, key bạn gửi không khớp với ANON_KEY hoặc SERVICE_ROLE_KEY trong .env, hoặc các key đó chưa được ký bằng JWT_SECRET hiện có trong .env. Với stack chưa từng chứa dữ liệu, hãy tạo lại cả 3 giá trị bằng sh utils/generate-keys.sh --update-env. Với stack đang chạy, hãy chạy script không có flag rồi trả lời no để script chỉ in ra các giá trị mới, chép JWT_SECRET, ANON_KEY và SERVICE_ROLE_KEY thủ công vào .env, đồng thời giữ nguyên POSTGRES_PASSWORD. Sau đó chạy sh run.sh recreate để các service đọc các giá trị mới.

Tôi có thể chạy Supabase self-hosted trên VPS 2 GB không?

Không ổn định. Tài liệu self-hosting của Supabase liệt kê 4 GB RAM là mức tối thiểu tính đến tháng 10 năm 2026. Đến tháng 7 năm 2026, stack dùng gần 3 GB RAM khi idle trong một phép đo có tính cả các logging service, vốn hiện đã chuyển thành tùy chọn bật thêm. Máy 2 GB sẽ bị out of memory killer dừng container, và bạn sẽ thấy exit code 137 trong docker compose ps. Dùng 8 GB cho production và xem 4 GB là mức tối thiểu cho phát triển cá nhân.

Supabase self-hosted có bao gồm edge functions không?

Có. File Compose bao gồm runtime functions dựa trên Deno và phục vụ mọi nội dung bạn đặt dưới ./volumes/functions. Nó không bao gồm global deployment network của hosted platform, nên functions của bạn chỉ chạy trên một server, tại một location.

Làm thế nào để kết nối trực tiếp đến database Postgres?

Dùng docker exec -it supabase-db psql -U postgres để mở interactive shell ngay trên server. Với client bên ngoài, hãy kết nối qua Supavisor trên port 5432 bằng user postgres.<POOLER_TENANT_ID> và POSTGRES_PASSWORD của bạn. Không mở port đó ra Internet. Hãy truy cập qua VPN hoặc SSH tunnel.

Vì sao email xác nhận auth của tôi lại trỏ đến localhost?

SITE_URL và API_EXTERNAL_URL trong .env vẫn giữ giá trị mặc định. Auth service tạo mọi link xác nhận và reset password từ 2 giá trị này, nên nó gửi đúng address đã được cấu hình. Đặt cả 2 thành public URL thực tế của bạn rồi tạo lại stack.

Bài viết này có hữu ích không?