
Trả lời nhanh: Trình tự an toàn: dựng stack self-host chạy song song → pg_dump từ cloud nạp vào VPS → chuyển file storage → kiểm auth và RLS trên môi trường mới → đổi URL + anon key phía app → theo dõi vài ngày rồi mới đóng project cloud. Người dùng và mật khẩu đi theo database (schema auth), nhưng phải kiểm luồng đăng nhập kỹ nhất trong mọi thứ.
Lý do các đội Việt rời Supabase Cloud thường không phải bất mãn, mà là bài toán chín dần: phí theo mức dùng tăng theo app lớn, yêu cầu dữ liệu nằm trong nước xuất hiện, hoặc đơn giản muốn ping database một con số thay vì vòng qua Singapore. Bài chốt cụm Supabase này là checklist di cư trọn vẹn: lấy hết những gì của bạn ra khỏi cloud và chạy tiếp trên VPS mà người dùng không nhận ra có cuộc chuyển nhà.
- Chạy song song hai môi trường trong suốt quá trình, cắt chuyển là bước cuối, không phải bước đầu
- Database: pg_dump qua connection string của cloud, nạp thẳng vào db self-host
- Storage: tải bucket về rồi đẩy vào volumes, dữ liệu file không nằm trong dump
- Auth đi theo schema trong database, nhưng luồng đăng nhập + OAuth phải kiểm từng đường
- Phía app chỉ đổi hai giá trị: URL và anon key, nhưng đổi ĐỦ mọi nơi chúng nằm
Chuẩn bị: dựng bến đỗ trước khi rời bến cũ
Dựng stack self-host hoàn chỉnh theo bài đầu cụm, secret riêng, domain API riêng (ví dụ api.ten-mien.vn), SMTP đã khai theo bài Auth. Phía cloud, lấy connection string của database (Settings → Database, dùng bản kết nối trực tiếp cho việc dump). Chọn khung giờ thấp điểm cho ngày cắt, và báo trước nếu app có ghi dữ liệu liên tục, kịch bản chuẩn giống bài chuyển website không downtime: dựng song song, đồng bộ, kiểm, rồi mới trỏ.
Kéo database: một lệnh dump, một lệnh nạp
# tren VPS (co pg_dump ban 15+ khop cloud):
pg_dump "postgresql://postgres:[email protected]:5432/postgres"
--no-owner --no-privileges -Fc -f cloud.dump
# nap vao stack self-host:
docker cp cloud.dump supabase-db:/tmp/
docker exec supabase-db pg_restore -U postgres -d postgres
--no-owner --clean --if-exists /tmp/cloud.dumpCờ --no-owner --no-privileges né xung đột role đặc thù của cloud; vài cảnh báo về schema hệ thống (extensions, graphql) khi restore là bình thường, thứ phải sạch là bảng dữ liệu của bạn và schema auth, storage. Kiểm ngay sau nạp: mở Studio self-host đếm số dòng vài bảng chủ lực so với cloud, và select count(*) from auth.users phải khớp.
Chuyển storage: phần không nằm trong dump
File trong bucket không đi theo pg_dump, dump chỉ mang metadata của chúng. Chuyển phần xác: viết script nhỏ dùng SDK liệt kê từng bucket và tải toàn bộ object từ cloud (dùng SERVICE_ROLE_KEY của cloud để đọc không vướng RLS), rồi upload lên self-host bằng SERVICE_ROLE_KEY mới, cách này giữ đúng đường dẫn để metadata đã nạp khớp với file. Kho nhỏ vài GB chạy một tối là xong; kho lớn thì chạy trước phần nguội vài ngày, ngày cắt chỉ đồng bộ phần phát sinh. Kiểm sau chuyển: mở app, các ảnh cũ phải hiện, ảnh vỡ hàng loạt nghĩa là lệch đường dẫn bucket, so lại tên bucket hai bên.
Auth và OAuth: kiểm kỹ nhất ở đây
Tin tốt: người dùng và mật khẩu băm nằm trong schema auth, đã sang theo database, không ai phải đặt lại mật khẩu. Việc còn lại là cấu hình quanh nó: JWT_SECRET mới nghĩa là mọi phiên cũ hết hiệu lực, người dùng đăng nhập lại một lần sau chuyển nhà (chấp nhận được, thông báo trước là lịch sự); OAuth Google/GitHub phải cập nhật redirect URI trong console của từng provider sang domain mới; SITE_URL và danh sách redirect trong .env trỏ về app thật. Bộ kiểm bắt buộc trước khi cắt: đăng nhập password cũ, đăng ký mới (email xác nhận về?), reset mật khẩu, mỗi provider OAuth một lượt, và một truy vấn có RLS bằng tài khoản thường, đủ xanh mới sang bước cuối.
Ngày cắt và tuần đầu sau chuyển
Ngày cắt: dump + nạp lần chót cho phần dữ liệu phát sinh (hoặc chấp nhận đóng băng ghi vài phút vào giờ thấp điểm), đổi URL + anon key phía app, đủ mọi nơi: web, mobile (cần phát hành bản mới thì cắt theo lịch phát hành), biến môi trường server, edge functions, rồi deploy. Tuần đầu: giữ project cloud ở trạng thái tạm dừng thay vì xóa (đường lùi miễn phí), theo dõi log auth và Kong mỗi ngày, bật cron backup theo bài backup ngay đêm đầu, vì kể từ giờ phút này, không còn ai backup hộ bạn nữa.
Danh sách kiểm trước ngày cắt
| Hạng mục | Kiểm gì | Cách kiểm |
|---|---|---|
| Cơ sở dữ liệu | Số dòng của các bảng chính khớp giữa hai bên | Đếm số dòng từng bảng ở cả hai, so danh sách |
| Tệp người dùng tải lên | Tổng số tệp và tổng dung lượng khớp | Đếm tệp trong kho ở cả hai bên |
| Tài khoản đăng nhập | Số tài khoản khớp, và mật khẩu cũ vẫn dùng được | Thử đăng nhập bằng vài tài khoản thật |
| Đăng nhập qua nhà cung cấp ngoài | Địa chỉ trả về đã khai đúng tên miền mới | Thử đăng nhập bằng tài khoản Google thật |
| Khóa và biến môi trường | Ứng dụng đã trỏ về địa chỉ mới | Kiểm cấu hình ứng dụng, không chỉ kiểm máy chủ |
| Tác vụ định kỳ và hàng đợi | Đã chuyển và đang chạy | Xem nhật ký của chúng sau 24 giờ |
Kéo dữ liệu, các lệnh chính
# 1. Kết xuất từ bên cũ pg_dump "postgresql://NGUOI_DUNG:MAT_KHAU@may-cu:5432/postgres" -Fc \ -f /root/chuyen/db.dump # 2. Nap vao ben moi docker compose exec -T db pg_restore -U postgres -d postgres --no-owner \ < /root/chuyen/db.dump # 3. Doi chieu so dong tung bang o ca hai ben psql -c "SELECT schemaname,relname,n_live_tup FROM pg_stat_user_tables ORDER BY relname;" # 4. Chay lai thong ke sau khi nap, neu khong truy van se cham bat thuong docker compose exec -T db psql -U postgres -c "ANALYZE;"
Lệnh cuối hay bị quên và gây hiểu nhầm tai hại: sau khi nạp dữ liệu, hệ thống chưa có thống kê nên chọn cách chạy truy vấn rất tệ. Người chuyển thấy bên mới chậm hơn bên cũ và tưởng máy yếu, trong khi chỉ thiếu một lệnh.
Ngày cắt và tuần đầu
- Chọn khung giờ ít người dùng nhất, và báo trước cho người dùng thật.
- Giữ bên cũ ở chế độ chỉ đọc ít nhất hai tuần, đừng đóng ngay. Đây là bảo hiểm rẻ nhất.
- Kéo bản sao đầu tiên của hệ thống mới ngay trong ngày cắt, trước khi có dữ liệu mới phát sinh.
- Theo dõi nhật ký lỗi ứng dụng mỗi ngày trong tuần đầu. Lỗi hay xuất hiện muộn thường nằm ở các luồng ít chạy: khôi phục mật khẩu, gửi thư, tác vụ cuối tháng.
Câu hỏi thường gặp
Migrate mất bao lâu cho một app cỡ vừa?
Database vài GB: dump + nạp tính bằng phút trên NVMe. Tổng thời gian thật nằm ở kiểm thử auth/storage và phối hợp ngày cắt, khung một buổi tối cho app nhỏ, một tuần đủng đỉnh (chạy song song) cho app có người dùng thật.
Người dùng có phải đặt lại mật khẩu sau khi chuyển không?
Không, mật khẩu băm đi theo schema auth. Họ chỉ phải đăng nhập lại một lần vì JWT_SECRET đổi làm phiên cũ hết hạn.
Edge Functions trên cloud có chuyển được không?
Code function nằm trong repo của bạn, deploy lại lên runtime functions của stack self-host. Kiểm các secret/biến môi trường function dùng, và lưu ý runtime self-host có thể lệch phiên bản với cloud, chạy thử từng function trước ngày cắt.
Chuyển ngược từ self-host lên cloud có làm được không?
Được, cùng bộ kỹ thuật theo chiều ngược (pg_dump từ VPS, nạp lên cloud qua connection string). Đây là bảo hiểm tâm lý tốt: quyết định self-host không phải cửa một chiều.
Bài viết liên quan
- Self-host Supabase trên VPS bằng Docker Compose từng bước
- Chuyển website sang VPS không downtime: quy trình 9 bước
- Supabase tự host: RAM và ổ đĩa cần bao nhiêu là đủ
- GHz là gì? Đọc thông số xung nhịp đúng cách khi thuê VPS
- Điều khiển Hermes Agent từ xa qua Telegram bot (setup + commands)
- Codex CLI trên VPS: cấu hình để không treo giữa tác vụ dài
- Cấu hình VPS tối thiểu để Claude Code chạy mượt cả ngày
- VPS US vs VPS VN: khi nào chọn loại nào? Use case 2026
- OpenClaw + Discord: build bot AI có ngữ cảnh nhớ cho community



