SSD Nodes Learn 8GB RAM — $66/năm
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-01

Hướng dẫn tự cài đặt Supabase trên VPS bằng Docker

Tự host Supabase trên VPS với Docker Compose. Bài viết hướng dẫn cách thay thế các secret mặc định, cấu hình 14 dịch vụ, yêu cầu RAM tối thiểu và quy trình backup dữ liệu an toàn.

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

Tự lưu trữ (self-hosting) Supabase nghĩa là chạy bộ Docker Compose chính thức trên server của riêng bạn: Postgres, một REST API phía trước nó, dịch vụ xác thực, lưu trữ tệp, realtime websockets và bảng điều khiển Studio. Bạn clone một repository, chỉnh sửa một tệp .env và khởi chạy khoảng mười bốn container cùng hoạt động như một dự án Supabase mà bạn kiểm soát.

Việc cài đặt rất ngắn gọn. Phần dễ xảy ra lỗi nằm ở tệp .env. Nó đi kèm với các secret demo được công khai trong repository, và một stack khởi chạy với các thiết lập mặc định đó sẽ mở cho bất kỳ ai tìm thấy nó. Hướng dẫn này bao gồm các secret bạn phải thay thế, mục đích của từng dịch vụ, dung lượng bộ nhớ mà stack thực sự cần và cách cập nhật nó mà không xóa cơ sở dữ liệu của bạn.

Nếu bạn chưa quen với Compose, hãy đọc Các kiến thức cơ bản về Docker Compose trên VPS trước. Mọi nội dung bên dưới đều giả định rằng docker compose version đã hiển thị phiên bản.

Stack này thực sự chứa những gì

Supabase không phải là một chương trình đơn lẻ. File Compose khởi chạy một tập hợp các dịch vụ riêng biệt trên cùng một network, và việc hiểu rõ từng dịch vụ sẽ giúp bạn chuyển đổi một danh sách tên container hỗn độn thành thứ mà bạn có thể debug được.

  • db là PostgreSQL với các extension của Supabase đã được load. Mọi dịch vụ khác đều giao tiếp với nó. Nếu container này không khỏe (unhealthy), mọi thứ khác cũng sẽ lỗi theo.
  • kong là API gateway. Nó lắng nghe trên port 8000 và định tuyến /rest/v1/, /auth/v1//storage/v1/ đến backend phù hợp. Đây là container duy nhất bạn nên expose ra ngoài.
  • rest là PostgREST. Nó đọc schema Postgres của bạn và phục vụ dưới dạng REST API, vì vậy một table mới sẽ trở thành một endpoint mới mà không cần viết code.
  • auth là GoTrue. Nó cấp phát các JSON web token (JWT) để xác thực người dùng của bạn.
  • storageimgproxy xử lý việc upload file và thay đổi kích thước ảnh.
  • realtime truyền tải các thay đổi trong database qua websockets.
  • studiometa là dashboard và admin API đứng sau nó.
  • analytics (Logflare) và vector thu thập log, và supavisor là trình quản lý kết nối (connection pooler) của Postgres.

Danh sách đó là lý do tại sao các con số về tài nguyên bên dưới lại như vậy. Bạn không chỉ chạy một database. Bạn đang chạy một database cộng với hàng tá dịch vụ hỗ trợ.

Định cỡ: lập kế hoạch cho 8 GB RAM

Stack này tiêu tốn khoảng 2,5 đến 3 GB bộ nhớ thường trú khi ở trạng thái nghỉ trên bản cài đặt mới, tính đến tháng 7 năm 2026, trước khi bạn thêm dữ liệu hoặc có traffic. Dịch vụ phân tích và tiến trình Studio Node.js là hai thành phần tiêu thụ tài nguyên lớn nhất. Một server 2 GB sẽ khởi động được các container nhưng sau đó sẽ bị kernel kết thúc do thiếu bộ nhớ, thường là analytics hoặc db, và triệu chứng là container bị kẹt ở trạng thái khởi động lại với exit code 137.

Hãy cấp 8 GB RAM và 4 vCPU cho bất kỳ hệ thống nào bạn cần chạy ổn định. 4 GB là đủ cho một instance phát triển cá nhân nếu bạn chấp nhận rằng việc thực hiện truy vấn nặng và phiên làm việc Studio cùng lúc sẽ bị chậm. Dung lượng đĩa cũng rất quan trọng, vì Postgres, volume lưu trữ và dữ liệu log đều nằm trong thư mục dự án. Hãy bắt đầu với 40 GB và theo dõi mức sử dụng.

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 thư mục dự án của riêng bạn. Sự tách biệt này rất quan trọng, vì nó đảm bảo rằng lệnh git pull sau này 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. Quá trình này sẽ kết thúc với mọi service được đánh dấu Pulled. Lỗi manifest unknown tại đây có nghĩa là image tag được ghim đã bị xóa ở phía upstream, và cách khắc phục là tải bản sao mới hơn của repository thay vì chỉnh sửa tag thủ công.

Những bí mật bạn phải thay đổi trước lần khởi động đầu tiên

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

Repository này cung cấp một trình tạo (generator) tạo ra mọi giá trị một cách chính xác, bao gồm cả hai 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 và các Logflare token vào .env. Nó cần openssl, vốn có sẵn trên mọi image Ubuntu thông thường.

Có hai giá trị mà nó không tự thiết lập, bạn phải chỉnh sửa thủ công trong .env:

  • POSTGRES_PASSWORD. Chỉ sử dụng chữ cái và chữ số. Dấu câu ở đây sẽ làm hỏng các connection string mà một số dịch vụ tạo ra bằng cách nối chuỗi, và lỗi này trông giống như lỗi xác thực thay vì lỗi phân tích cú pháp, khiến người dùng tìm kiếm sai chỗ.
  • DASHBOARD_USERNAMEDASHBOARD_PASSWORD. Đây là thông tin xác thực cơ bản cho Studio. Mật khẩu mặc định được cung cấp theo nghĩa đen là this_password_is_insecure_and_should_be_updated.

Hãy hiểu lý do tại sao ANON_KEYSERVICE_ROLE_KEY không thể tự đặt tùy ý. Cả hai đều là các JWT được ký bằng JWT_SECRET. Gateway sẽ xác minh chữ ký đó trên mọi request, vì vậy một key không khớp với secret của bạn sẽ bị từ chối với lỗi {"message":"Invalid authentication credentials"}. Đây là lỗi tự host phổ biến nhất: người vận hành đã thay đổi JWT_SECRET nhưng vẫn giữ lại các demo key. Hãy luôn tạo cả ba giá trị này cùng lúc.

Hãy coi SERVICE_ROLE_KEY như một mật khẩu root. Nó bỏ qua hoàn toàn row level security. Nó chỉ được phép nằm trong code phía server và không được xuất hiện ở bất kỳ nơi nào khác.

Thiết lập SITE_URLAPI_EXTERNAL_URL thành địa chỉ mà người dùng của bạn sẽ thực sự truy cập, ví dụ như https://supabase.example.com. Auth xây dựng các liên kết xác nhận email và callback OAuth từ các giá trị đó, vì vậy nếu để chúng là http://localhost:8000, mọi người dùng của bạn sẽ bị điều hướng về chính máy của họ.

Sau đó, hãy kiểm tra lại những gì bạn đã có:

sh run.sh secrets

Khởi động và kiểm tra trạng thái hoạt động

sh run.sh start
docker compose ps

run.sh start bao bọc docker compose up -d --wait, vì vậy nó sẽ không trả về kết quả cho đến khi các kiểm tra trạng thái hoàn tất. Mọi dịch vụ 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 tập lệnh 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 một container liên tục khởi động lại, hãy đọc log của nó theo tên dịch vụ:

docker compose logs db
docker compose logs auth

Studio sau đó sẽ chạy trên cổng 8000 và yêu cầu tên người dùng cùng mật khẩu dashboard mà bạn đã thiết lập.

Không để cổng 8000 trên internet công cộng

Kong 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 truyền qua mạng dưới dạng văn bản rõ (clear text), và thông tin xác thực của Studio là xác thực cơ bản (basic authentication), vốn chỉ là mã hóa base64 chứ không phải mã hóa bảo mật.

Hãy đặt một reverse proxy phía trước, kết thúc TLS (transport layer security) tại đó và bind Kong vào địa chỉ loopback để không dịch vụ nào khác có thể truy cập nó. Trong docker-compose.yml, ánh xạ cổng kong sẽ trở thành 127.0.0.1:8000:8000 và proxy sẽ chuyển tiếp đến đó. Traefik phía trước các ứng dụng Compose bao gồm phần cấu hình chứng chỉ.

Đồng thời hãy đóng các cổng còn lại tại firewall, vì Docker xuất bản các cổng bằng cách tự ghi các quy tắc iptables mà cấu hình ufw thông thường không nhìn thấy. Bẫy này được giải thích trong tại sao các container Docker bỏ qua các quy tắc ufw của bạn.

Sao lưu cơ sở dữ liệu, không phải thư mục

Dữ liệu Postgres nằm trong một bind mount tại ./volumes/db/data. Việc sao chép thư mục đó khi container đang chạy sẽ tạo ra một bản sao bị lỗi (torn copy), vì Postgres đệm các thao tác ghi và các tệp trên đĩa chỉ nhất quán tại thời điểm checkpoint. Việc khôi phục từ bản sao này thường sẽ hoạt động, nhưng đôi khi sẽ âm thầm làm mất các giao dịch cuối cùng, đây là kiểu lỗi tồi tệ nhất đối với một bản sao lưu.

Hãy sử dụng dump thay thế. pg_dumpall chạy bên trong container và tạo ra một snapshot nhất quán:

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

Hãy kiểm tra để đảm bảo tệp không trống trước khi tin tưởng nó. Sau đó, hãy chuyển các bản dump đó ra khỏi server theo lịch trình, đây chính là mục đích của sao lưu ngoại vi được mã hóa bằng restic. Hãy sao lưu .env của bạn cùng lúc. Mất JWT_SECRET đồng nghĩa với việc mọi token đã cấp đều trở nên không hợp lệ và mọi bí mật đã mã hóa được lưu trữ đều không thể đọc được.

Các tệp đã tải lên nằm trong ./volumes/storage, đây là các tệp thông thường, vì vậy việc sao chép đơn thuần là ổn.

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

Supabase ghim các phiên bản image trong docker-compose.yml, vì vậy mọi thứ sẽ không thay đổi cho đến khi bạn thực hiện. Hãy luôn thực hiện dump dữ liệu trước mỗi lần cập nhật.

docker compose pull
sh run.sh recreate

recreate sẽ dừng stack và khởi động lại trên các image mới. Dữ liệu của bạn vẫn an toàn vì nó nằm trong các bind mount trên host, không phải bên trong các container. Hãy đọc CHANGELOG.md trong repository trước khi thực hiện nâng cấp phiên bản lớn, vì các bản nâng cấp lớn của Postgres không tự động và yêu cầu thực hiện dump và restore.

Để áp dụng các thay đổi đối với chính file Compose, hãy clone lại upstream repository và copy thư mục docker của nó đè lên dự án của bạn, lưu ý không ghi đè lên .env.

Thao tác reset toàn bộ, vốn sẽ xóa sạch mọi thứ bao gồm cả database, là một script riêng biệt và nó sẽ yêu cầu xác nhận:

sh reset.sh

FAQ

Tại sao các lệnh gọi API của tôi trả về "Invalid authentication credentials"?

ANON_KEY hoặc SERVICE_ROLE_KEY của bạn không được ký bằng JWT_SECRET hiện có trong .env. Gateway xác thực chữ ký trên mỗi yêu cầu và từ chối nếu không khớp. Hãy tạo lại cả ba cùng với sh utils/generate-keys.sh --update-env, sau đó chạy sh run.sh recreate để các dịch vụ đọc các giá trị mới.

Tôi có thể chạy Supabase tự lưu trữ trên VPS 2 GB không?

Không ổn định. Stack này tiêu tốn gần 3 GB RAM tính đến tháng 7 năm 2026 vì nó chạy khoảng mười bốn dịch vụ, do đó máy chủ 2 GB sẽ bị trình quản lý bộ nhớ (OOM killer) tắt các container và bạn sẽ thấy mã thoát 137 trong docker compose ps. Hãy sử dụng 8 GB cho môi trường production và coi 4 GB là mức tối thiểu cho việc phát triển cá nhân.

Supabase tự lưu trữ có bao gồm edge functions không?

Có. Tệp Compose bao gồm runtime cho các hàm dựa trên Deno và nó phục vụ bất kỳ thứ gì bạn đặt trong ./volumes/functions. Nó không bao gồm mạng triển khai toàn cầu như nền tảng được lưu trữ (hosted platform), vì vậy các hàm của bạn chỉ chạy trên một máy chủ duy nhất tại một vị trí.

Làm cách nào để kết nối trực tiếp với cơ sở dữ liệu Postgres?

Sử dụng docker exec -it supabase-db psql -U postgres để có shell tương tác ngay trên máy chủ. Đối với client bên ngoài, hãy kết nối thông qua Supavisor tại cổng 5432 với người dùng postgres.<POOLER_TENANT_ID>POSTGRES_PASSWORD của bạn. Đừng mở cổng đó ra internet. Hãy truy cập nó thông qua VPN hoặc SSH tunnel.

Tại sao các email xác thực của tôi lại dẫn đến localhost?

SITE_URLAPI_EXTERNAL_URL trong .env vẫn đang ở giá trị mặc định. Dịch vụ xác thực tạo mọi liên kết xác nhận và đặt lại mật khẩu từ hai giá trị đó, vì vậy nó gửi địa chỉ mà nó được yêu cầu gửi. Hãy đặt cả hai thành URL công khai thực tế của bạn và tạo lại stack.