วิธีติดตั้ง Supabase บน VPS ด้วย Docker แบบละเอียด
เรียนรู้วิธีรัน Supabase บนเซิร์ฟเวอร์ส่วนตัวผ่าน Docker Compose ครอบคลุมการตั้งค่าไฟล์ .env ที่ปลอดภัย การจัดการ 14 บริการหลัก ข้อกำหนด RAM และขั้นตอนการสำรองข้อมูลที่ถูกต้อง
สิ่งที่คุณกำลังสร้าง
การทำ Self-hosting สำหรับ Supabase หมายถึงการรัน Docker Compose stack อย่างเป็นทางการบนเซิร์ฟเวอร์ของคุณเอง ซึ่งประกอบด้วย Postgres, REST API ที่อยู่ด้านหน้า, บริการยืนยันตัวตน (auth service), พื้นที่จัดเก็บไฟล์, ระบบ realtime websockets และแดชบอร์ด Studio คุณเพียงแค่โคลน repository แก้ไขไฟล์ .env หนึ่งไฟล์ และเริ่มการทำงานของคอนเทนเนอร์ประมาณสิบสี่ตัว ซึ่งทั้งหมดจะทำงานร่วมกันเสมือนโปรเจกต์ Supabase ที่คุณควบคุมเอง
การติดตั้งนั้นสั้นและรวดเร็ว ส่วนที่มักจะเกิดปัญหาคือไฟล์ .env ซึ่งมาพร้อมกับค่า secret ตัวอย่างที่ถูกเผยแพร่ไว้ใน repository หากเริ่มการทำงานของ stack ด้วยค่าเริ่มต้นเหล่านั้น ระบบจะเปิดกว้างให้ทุกคนที่พบเข้าถึงได้ คู่มือนี้จะครอบคลุมถึงค่า secret ที่คุณต้องเปลี่ยน วัตถุประสงค์ของแต่ละบริการ ปริมาณหน่วยความจำที่ stack ต้องการจริง และวิธีการอัปเดตโดยไม่ลบฐานข้อมูลของคุณ
หากคุณยังใหม่กับ Compose โปรดอ่าน พื้นฐาน Docker Compose บน VPS ก่อน เนื้อหาทั้งหมดด้านล่างนี้ตั้งอยู่บนสมมติฐานว่า docker compose version แสดงเวอร์ชันออกมาได้สำเร็จแล้ว
สิ่งที่อยู่ใน stack จริงๆ
Supabase ไม่ใช่โปรแกรมเดียว ไฟล์ Compose จะเริ่มการทำงานของบริการแยกส่วนหลายรายการบนเครือข่ายเดียวกัน การเข้าใจว่าแต่ละส่วนคืออะไรจะช่วยให้คุณเปลี่ยนรายชื่อ container ที่ดูซับซ้อนให้กลายเป็นสิ่งที่สามารถตรวจสอบและแก้ไขปัญหาได้
dbคือ PostgreSQL ที่โหลดส่วนขยายของ Supabase ไว้ บริการอื่นๆ ทั้งหมดจะติดต่อกับส่วนนี้ หาก container นี้ไม่พร้อมใช้งาน บริการอื่นทั้งหมดก็จะล้มเหลวตามไปด้วยkongคือ API gateway ทำหน้าที่ฟังคำขอที่พอร์ต 8000 และส่งต่อ/rest/v1/,/auth/v1/และ/storage/v1/ไปยัง backend ที่ถูกต้อง นี่คือ container เดียวที่คุณควรเปิดให้เข้าถึงจากภายนอกrestคือ PostgREST ทำหน้าที่อ่าน schema ของ Postgres และให้บริการในรูปแบบ REST API ดังนั้นเมื่อคุณสร้างตารางใหม่ ก็จะได้ endpoint ใหม่โดยไม่ต้องเขียนโค้ดเพิ่มauthคือ GoTrue ทำหน้าที่ออก JSON web tokens (JWT) เพื่อระบุตัวตนของผู้ใช้งานstorageและimgproxyจัดการเรื่องการอัปโหลดไฟล์และการปรับขนาดรูปภาพrealtimeทำหน้าที่สตรีมการเปลี่ยนแปลงของฐานข้อมูลผ่าน websocketsstudioและmetaคือ dashboard และ admin API ที่อยู่เบื้องหลังanalytics(Logflare) และvectorทำหน้าที่รวบรวม log ส่วนsupavisorคือตัวจัดการ connection pool ของ Postgres
รายการข้างต้นคือเหตุผลว่าทำไมตัวเลขทรัพยากรที่ระบุด้านล่างจึงเป็นค่าดังกล่าว คุณไม่ได้กำลังรันแค่ฐานข้อมูล แต่คุณกำลังรันฐานข้อมูลควบคู่ไปกับบริการสนับสนุนอีกนับสิบรายการ
การจัดสรรขนาด: วางแผนสำหรับ RAM ขนาด 8 GB
ณ เดือนกรกฎาคม 2026 stack นี้ใช้หน่วยความจำแบบ resident memory ขณะไม่ได้ใช้งานอยู่ที่ประมาณ 2.5 ถึง 3 GB ในการติดตั้งใหม่ ก่อนที่จะมีการเพิ่มข้อมูลหรือมี traffic ของคุณเอง บริการ analytics และกระบวนการ Node.js ของ Studio เป็นส่วนที่ใช้ทรัพยากรมากที่สุดสองรายการ เซิร์ฟเวอร์ขนาด 2 GB จะเริ่มการทำงานของคอนเทนเนอร์ได้ แต่จากนั้นจะสูญเสียคอนเทนเนอร์ไปหนึ่งรายการเนื่องจาก kernel out of memory killer ซึ่งมักจะเป็น analytics หรือ db โดยมีอาการคือคอนเทนเนอร์ค้างอยู่ในสถานะรีสตาร์ทด้วย exit code 137
ควรจัดสรร RAM ขนาด 8 GB และ 4 vCPU สำหรับทุกบริการที่คุณต้องใช้งานจริง ขนาด 4 GB สามารถใช้ได้สำหรับ instance เพื่อการพัฒนาส่วนตัว หากคุณยอมรับได้ว่าการรัน query หนักๆ พร้อมกับการใช้งาน Studio จะทำให้ระบบทำงานช้าลง พื้นที่จัดเก็บข้อมูลก็มีความสำคัญเช่นกัน เนื่องจาก Postgres, volume สำหรับจัดเก็บข้อมูล และข้อมูล log ทั้งหมดจะอยู่ในไดเรกทอรีของโปรเจกต์ ให้เริ่มต้นที่ 40 GB แล้วคอยตรวจสอบการใช้งาน การนับจำนวนบริการก่อนเลือกแผนการใช้งานเป็นนิสัยที่ดีสำหรับการทำ self-host ทุกประเภท เนื่องจาก PhotoPrism และ Immich มีความต้องการ RAM ขั้นต่ำที่สูงกว่าที่ระบุไว้ในหน้าเริ่มต้นใช้งานอย่างรวดเร็ว (quick start) อย่างมาก
การติดตั้ง: โคลน repository อย่างเป็นทางการ
เส้นทางที่รองรับคือการคัดลอกไดเรกทอรี docker ออกจาก repository หลักไปยังไดเรกทอรีโปรเจกต์ของคุณเอง การแยกส่วนนี้มีความสำคัญ เพราะหมายความว่าการทำ git pull ในภายหลังจะไม่เขียนทับ .env ของคุณ
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 จะดาวน์โหลดอิมเมจขนาดหลายกิกะไบต์ กระบวนการควรสิ้นสุดโดยที่ทุกบริการมีสถานะเป็น Pulled หากเกิดข้อผิดพลาด manifest unknown ในขั้นตอนนี้ หมายความว่าแท็กอิมเมจที่ระบุไว้ถูกลบออกจากต้นทางแล้ว วิธีแก้ไขคือการดึง repository เวอร์ชันใหม่มาแทนการแก้ไขแท็กด้วยตนเอง
ความลับที่คุณต้องเปลี่ยนก่อนการเริ่มทำงานครั้งแรก
ให้ดำเนินการขั้นตอนนี้ก่อนที่คุณจะเริ่ม stack ไม่ใช่หลังจากนั้น ค่าเหล่านี้หลายรายการจะถูกเขียนลงในข้อมูลเมื่อบูตครั้งแรก ดังนั้นการเปลี่ยนในภายหลังหมายถึงการต้องรีเซ็ตฐานข้อมูลใหม่
repository นี้มาพร้อมกับตัวสร้างที่จะสร้างค่าทุกอย่างได้อย่างถูกต้อง รวมถึง API keys สองรายการที่ต้องลงนามด้วย JWT secret ใหม่ของคุณ
sh utils/generate-keys.sh --update-envสคริปต์ดังกล่าวจะเขียนค่าใหม่สำหรับ JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY และ Logflare tokens ลงใน .env โดยสคริปต์นี้ต้องการ openssl ซึ่งมีอยู่แล้วใน Ubuntu image ปกติทั่วไป
มีสองค่าที่สคริปต์ไม่ได้ตั้งค่าให้ และคุณต้องแก้ไขด้วยตนเองใน .env:
POSTGRES_PASSWORDให้ใช้เฉพาะตัวอักษรและตัวเลขเท่านั้น การใช้เครื่องหมายวรรคตอนจะทำให้ connection strings ที่บริการต่างๆ สร้างขึ้นจากการเชื่อมสตริงเกิดข้อผิดพลาด และความล้มเหลวที่เกิดขึ้นจะมีลักษณะเหมือนข้อผิดพลาดในการยืนยันตัวตน (authentication error) มากกว่าข้อผิดพลาดในการแยกวิเคราะห์ (parsing error) ซึ่งจะทำให้ผู้ใช้งานเสียเวลาไปตรวจสอบในจุดที่ผิดDASHBOARD_USERNAMEและDASHBOARD_PASSWORDนี่คือข้อมูลประจำตัวสำหรับการยืนยันตัวตนพื้นฐาน (basic authentication) สำหรับ Studio โดยรหัสผ่านเริ่มต้นที่มากับระบบคือthis_password_is_insecure_and_should_be_updated
โปรดทำความเข้าใจว่าเหตุใด ANON_KEY และ SERVICE_ROLE_KEY จึงไม่สามารถกำหนดขึ้นเองได้ ทั้งสองค่าเป็น JWT ที่ลงนามด้วย JWT_SECRET โดย gateway จะตรวจสอบลายเซ็นนั้นในทุกคำขอ ดังนั้นคีย์ที่ไม่ตรงกับ secret ของคุณจะถูกปฏิเสธด้วย {"message":"Invalid authentication credentials"} นี่คือความล้มเหลวที่พบบ่อยที่สุดในการทำ self-hosting คือผู้ดูแลระบบเปลี่ยน JWT_SECRET แต่ยังคงใช้คีย์ตัวอย่างเดิมอยู่ ดังนั้นควรสร้างทั้งสามค่านี้พร้อมกันเสมอ
ให้ปฏิบัติต่อ SERVICE_ROLE_KEY เหมือนกับรหัสผ่าน root เพราะมันสามารถข้ามผ่านระบบ row level security ได้โดยสมบูรณ์ ค่านี้ควรอยู่ในโค้ดฝั่งเซิร์ฟเวอร์เท่านั้น ห้ามนำไปใช้ที่อื่น
ตั้งค่า SITE_URL และ API_EXTERNAL_URL ให้เป็นที่อยู่ที่ผู้ใช้งานของคุณจะเข้าถึงได้จริง เช่น https://supabase.example.com เนื่องจากระบบ Auth จะสร้างลิงก์ยืนยันอีเมลและ OAuth callback จากค่าเหล่านี้ ดังนั้นหากปล่อยให้เป็น http://localhost:8000 จะทำให้ผู้ใช้งานทุกคนถูกส่งไปยังเครื่องของตนเองแทน
จากนั้นให้ตรวจสอบสิ่งที่คุณตั้งค่าไว้:
sh run.sh secretsเริ่มการทำงานและตรวจสอบสถานะความพร้อม
sh run.sh start
docker compose psrun.sh start จะครอบ docker compose up -d --wait ไว้ ดังนั้นคำสั่งจะไม่ส่งค่ากลับจนกว่าการตรวจสอบความพร้อมจะเสร็จสิ้น บริการทุกตัวควรแสดงสถานะเป็น running (healthy) หรือ running การบูตครั้งแรกจะใช้เวลาประมาณ 2 ถึง 4 นาที เนื่องจาก Postgres ต้องรันสคริปต์เริ่มต้นก่อนที่บริการอื่นจะสามารถเชื่อมต่อได้
หากคอนเทนเนอร์มีการรีสตาร์ทตัวเอง ให้ตรวจสอบ log ตามชื่อบริการ:
docker compose logs db
docker compose logs authจากนั้น Studio จะพร้อมใช้งานที่พอร์ต 8000 โดยระบบจะเรียกให้คุณระบุชื่อผู้ใช้และรหัสผ่านสำหรับแดชบอร์ดที่คุณได้ตั้งค่าไว้
ห้ามเปิดพอร์ต 8000 สู่สาธารณะ
Kong ที่ทำงานบนพอร์ต 8000 ใช้โปรโตคอล HTTP แบบปกติ ข้อมูล API key และรหัสผ่านของผู้ใช้ทั้งหมดจะถูกส่งผ่านเครือข่ายในรูปแบบข้อความธรรมดา นอกจากนี้ข้อมูลรับรองของ Studio ยังใช้การยืนยันตัวตนแบบ basic authentication ซึ่งเป็นการเข้ารหัสแบบ base64 ไม่ใช่การเข้ารหัสลับข้อมูล
ให้ติดตั้ง reverse proxy ไว้ด้านหน้าเพื่อทำ TLS (transport layer security) termination และผูก Kong ไว้กับ loopback address เพื่อป้องกันไม่ให้บุคคลภายนอกเข้าถึงได้ ใน docker-compose.yml การแมปพอร์ต kong จะเปลี่ยนเป็น 127.0.0.1:8000:8000 และ proxy จะส่งต่อคำขอไปยังพอร์ตดังกล่าว เนื้อหาใน Traefik สำหรับแอปพลิเคชัน Compose หลายรายการ จะอธิบายรายละเอียดเกี่ยวกับการจัดการใบรับรอง
นอกจากนี้ควรปิดพอร์ตอื่นที่ firewall ด้วย เนื่องจาก Docker จะเผยแพร่พอร์ตโดยการเขียนกฎ iptables ของตนเอง ซึ่งการตั้งค่า ufw แบบทั่วไปจะไม่สามารถมองเห็นกฎเหล่านี้ได้ ปัญหานี้อธิบายไว้ใน เหตุใดคอนเทนเนอร์ Docker จึงเพิกเฉยต่อกฎ ufw ของคุณ
สำรองข้อมูลฐานข้อมูล อย่าสำรองแค่ไดเรกทอรี
ข้อมูลของ Postgres ถูกเก็บไว้ใน bind mount ที่ ./volumes/db/data การคัดลอกไดเรกทอรีดังกล่าวในขณะที่คอนเทนเนอร์กำลังทำงานจะทำให้ได้ข้อมูลที่ไม่สมบูรณ์ (torn copy) เนื่องจาก Postgres มีการบัฟเฟอร์การเขียนข้อมูล และไฟล์บนดิสก์จะมีความสอดคล้องกันก็ต่อเมื่อถึงจุด checkpoint เท่านั้น การกู้คืนข้อมูลจากวิธีนี้มักจะทำได้สำเร็จ แต่บางครั้งอาจทำให้ธุรกรรมล่าสุดสูญหายไปโดยที่คุณไม่ทราบ ซึ่งถือเป็นความล้มเหลวที่เลวร้ายที่สุดสำหรับการสำรองข้อมูล
ให้ใช้วิธีการ dump ข้อมูลแทน pg_dumpall จะทำงานภายในคอนเทนเนอร์และสร้าง snapshot ที่มีความสอดคล้องกันของข้อมูล:
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sqlตรวจสอบให้แน่ใจว่าไฟล์ที่ได้ไม่มีขนาดเป็นศูนย์ก่อนที่จะนำไปใช้งาน จากนั้นให้ส่งไฟล์ dump เหล่านี้ออกจากเซิร์ฟเวอร์ตามกำหนดเวลา ซึ่งเป็นหน้าที่ของ การสำรองข้อมูลนอกสถานที่แบบเข้ารหัสด้วย restic และควรสำรองข้อมูล .env ไปพร้อมกันด้วย การสูญเสีย JWT_SECRET จะทำให้ token ที่ออกไปทั้งหมดใช้งานไม่ได้ และข้อมูลลับที่เข้ารหัสไว้จะไม่สามารถอ่านได้อีกต่อไป
ไฟล์ที่อัปโหลดจะถูกเก็บไว้ใน ./volumes/storage ซึ่งเป็นไฟล์ทั่วไป ดังนั้นการคัดลอกไฟล์โดยตรงจึงสามารถทำได้ตามปกติ
การอัปเดตโดยไม่ให้ข้อมูลสูญหาย
Supabase ระบุเวอร์ชันของ image ไว้ใน docker-compose.yml ดังนั้นจะไม่มีอะไรเปลี่ยนจนกว่าคุณจะเป็นผู้เปลี่ยนเอง แนวทางการระบุเวอร์ชันตายตัวนี้ควรนำไปใช้กับ stack ใดก็ตามที่คุณประกอบขึ้นเอง ซึ่งเป็นเหตุผลที่ RustDesk relay ที่ self-host ระบุเวอร์ชันของ image สำหรับเซิร์ฟเวอร์ทั้ง 2 รายการ แทนการติดตาม tag ที่เปลี่ยนแปลงอยู่เสมอ การอัปเกรดควรเป็นสิ่งที่คุณเลือกทำในช่วงเช้าที่มีเวลารับมือกับงานดังกล่าว ก่อนดำเนินการทุกครั้ง ให้สร้าง dump ไว้ก่อน
docker compose pull
sh run.sh recreaterecreate จะหยุดการทำงานของ stack และเริ่มใหม่ด้วยอิมเมจเวอร์ชันล่าสุด ข้อมูลของคุณจะยังคงอยู่เนื่องจากถูกเก็บไว้ใน bind mounts บนโฮสต์ ไม่ได้เก็บไว้ภายในคอนเทนเนอร์ โปรดอ่าน CHANGELOG.md ใน repository ก่อนการอัปเกรดเวอร์ชันหลัก เนื่องจากกระบวนการอัปเกรดเวอร์ชันหลักของ Postgres ไม่ได้เกิดขึ้นโดยอัตโนมัติและจำเป็นต้องใช้วิธีการ dump และ restore ข้อมูล
หากต้องการนำการเปลี่ยนแปลงในไฟล์ Compose มาใช้งาน ให้โคลน upstream repository อีกครั้งแล้วคัดลอกไดเรกทอรี docker ไปทับในโปรเจกต์ของคุณ โดยต้องระมัดระวังไม่ให้เขียนทับไฟล์ .env
สำหรับการรีเซ็ตระบบแบบสมบูรณ์ซึ่งจะทำลายข้อมูลทั้งหมดรวมถึงฐานข้อมูล จะต้องใช้สคริปต์แยกต่างหากและระบบจะขอการยืนยันก่อนดำเนินการเสมอ:
sh reset.shFAQ
ทำไมการเรียกใช้ API ของฉันถึงได้รับข้อความ "Invalid authentication credentials"?
ANON_KEY หรือ SERVICE_ROLE_KEY ของคุณไม่ได้ถูกลงนามด้วย JWT_SECRET ที่อยู่ใน .env ในปัจจุบัน เกตเวย์จะตรวจสอบลายเซ็นในทุกคำขอและปฏิเสธหากข้อมูลไม่ตรงกัน ให้สร้างทั้งสามค่าขึ้นใหม่พร้อมกันด้วย sh utils/generate-keys.sh --update-env จากนั้นรัน sh run.sh recreate เพื่อให้เซอร์วิสต่างๆ อ่านค่าใหม่เหล่านั้น
ฉันสามารถรัน Supabase แบบ self-hosted บน VPS ขนาด 2 GB ได้หรือไม่?
ไม่สามารถรันได้อย่างเสถียร สแต็กนี้ใช้หน่วยความจำขณะว่างงานใกล้เคียง 3 GB ณ เดือนกรกฎาคม 2026 เนื่องจากมีการรันเซอร์วิสประมาณสิบสี่รายการ ดังนั้นเครื่องขนาด 2 GB จะสูญเสียคอนเทนเนอร์ไปเพราะ out of memory killer และคุณจะพบ exit code 137 ใน docker compose ps ควรใช้ขนาด 8 GB สำหรับการใช้งานจริง และถือว่า 4 GB เป็นขั้นต่ำสำหรับการพัฒนาคนเดียว
Supabase แบบ self-hosted มี edge functions รวมอยู่ด้วยหรือไม่?
มี ไฟล์ Compose ได้รวมรันไทม์ของฟังก์ชันที่ใช้ Deno ไว้แล้ว และมันจะให้บริการทุกอย่างที่คุณวางไว้ภายใต้ ./volumes/functions อย่างไรก็ตาม มันไม่ได้รวมเครือข่ายการปรับใช้ระดับโลกเหมือนแพลตฟอร์มแบบ hosted ดังนั้นฟังก์ชันของคุณจะรันอยู่บนเซิร์ฟเวอร์เดียวของคุณ ในตำแหน่งเดียวเท่านั้น
ฉันจะเชื่อมต่อกับฐานข้อมูล Postgres โดยตรงได้อย่างไร?
ใช้ docker exec -it supabase-db psql -U postgres สำหรับเชลล์แบบโต้ตอบบนตัวเซิร์ฟเวอร์เอง สำหรับไคลเอนต์ภายนอก ให้เชื่อมต่อผ่าน Supavisor ที่พอร์ต 5432 ด้วยผู้ใช้ postgres.<POOLER_TENANT_ID> และ POSTGRES_PASSWORD ของคุณ อย่าเปิดพอร์ตนั้นออกสู่สาธารณะ ให้เข้าถึงผ่าน VPN หรือ SSH tunnel แทน
ทำไมลิงก์ในอีเมลยืนยันตัวตนของฉันถึงชี้ไปที่ localhost?
SITE_URL และ API_EXTERNAL_URL ใน .env ยังคงเป็นค่าเริ่มต้น เซอร์วิสยืนยันตัวตนจะสร้างลิงก์ยืนยันและรีเซ็ตรหัสผ่านทุกรายการจากสองค่านี้ มันจึงส่งที่อยู่ที่ได้รับคำสั่งให้ส่ง ให้ตั้งค่าทั้งสองเป็น URL สาธารณะจริงของคุณแล้วสร้างสแต็กขึ้นใหม่