Branching และ Migrations Workflow
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Supabase branch คือ preview copy เต็มรูปแบบของ project คุณที่ผูกกับ Git branch หรือ pull request — มี Postgres schema แยกเป็นของตัวเอง และถ้าต้องการก็มี seed data เป็นของตัวเองด้วย — เพื่อให้คุณ test schema change แบบแยกส่วนก่อนที่จะไปแตะ production จริง เหมือนกับที่ preview deployment แยกส่วน app code ของคุณนั่นแหละ
Supabase branch คืออะไรกันแน่
หัวข้อที่มีชื่อว่า “Supabase branch คืออะไรกันแน่”ถ้าไม่มี branching การ “test schema change” ก็มักจะหมายถึงการรัน change นั้นเข้ากับ staging database ที่ใช้ร่วมกัน หรือแย่กว่านั้นคือรันตรงเข้า production เลย Supabase branch แทนที่จะทำแบบนั้น จะ provision Postgres instance ที่แยกจริง ๆ ตลอดอายุของ Git branch หรือ pull request นั้น — มี schema เป็นของตัวเอง, Data API เป็นของตัวเอง, Auth กับ Storage เป็นของตัวเอง และถ้าคุณขอ ก็จะได้ clone ของข้อมูล production ด้วย
# Create a persistent preview branch (stays around across pushes, not tied to a single PR)supabase branches create --persistent
# Create a preview branch for a pull request, cloning production data into itsupabase branches create feature-add-comments --with-data
# List every preview branch on the linked projectsupabase branches listนี่ไม่ใช่ feature flag ที่เปิด-ปิดอยู่ข้างใน database เดียวที่ใช้ร่วมกัน — นี่คือ database ที่แยกออกมาจริง ๆ ซึ่งเป็นเหตุผลว่าทำไมถึงปลอดภัยที่จะรัน migration แบบทำลายข้อมูล, seed test data แปลก ๆ หรือลอง alter table ที่เสี่ยง ๆ ได้ โดยไม่กระทบ production เลย
Migration คือรูปแบบไฟล์ที่ผูก branch เข้ากับ production
หัวข้อที่มีชื่อว่า “Migration คือรูปแบบไฟล์ที่ผูก branch เข้ากับ production”branch จะยังมีประโยชน์อยู่ก็ต่อเมื่อ schema change ที่ test บน branch นั้นเอาไป replay ซ้ำที่อื่นได้อย่างน่าเชื่อถือ นั่นคือหน้าที่ของ migration — ไฟล์ SQL ธรรมดา ที่ถูก commit เข้า version control และ apply ตามลำดับ
# Create a new, timestamped SQL migration file under supabase/migrations/supabase migration new add_comments_table
# Apply all pending local migrations to the linked project or branchsupabase db push
# Capture a schema change made through the Studio GUI as a migration file after the factsupabase db diff --schema publicsupabase migration new ให้ไฟล์ .sql เปล่า ๆ ที่มี timestamp กำกับ ให้คุณเขียนคำสั่ง create table (หรือ alter table) ลงไปเอง supabase db diff ครอบคลุมกรณีตรงข้าม คือมีคนแก้ผ่าน Studio GUI แล้วคุณอยากจับ change นั้นเป็น migration file ที่เป็นทางการ แทนที่จะปล่อยให้ค้างอยู่แค่ใน database ไม่ว่าจะทางไหน supabase db push จะ apply ทุก migration ที่ยังไม่เคยรันเข้ากับ project หรือ branch ที่ชี้ไป — คำสั่งเดียวกัน ไม่ว่าเป้าหมายจะเป็น preview branch หรือ production
Deploy migration ผ่าน CI ไม่ใช่มือคน
หัวข้อที่มีชื่อว่า “Deploy migration ผ่าน CI ไม่ใช่มือคน”คุณค่าของ branching แทบจะหายไปถ้าหลังจาก test migration บน branch แล้ว มีคนเปิด SQL editor แล้ววาง SQL เข้า production เอง จุดนี้แหละที่ typo, ไฟล์ที่ลืมรัน และ migration ที่ผิดลำดับมักจะแอบเข้ามา ทางแก้คือทำให้ supabase db push เป็นส่วนหนึ่งของ CI pipeline เดียวกับที่ deploy application code ของคุณ เพื่อให้ schema change ถูก ship อัตโนมัติและตามลำดับทุกครั้งที่ pull request ถูก merge
# A representative CI step — runs after tests pass, before or alongside the app deploysupabase link --project-ref "$SUPABASE_PROJECT_REF"supabase db pushเมื่อตั้งค่าแบบนี้แล้ว migration file ที่ merge เข้า main branch จะถูก apply เข้า production แบบเดียวกันทุกครั้ง โดย automated process ตัวเดียวกับที่ test บน preview branch มาแล้ว — ไม่มีคนมารัน SQL แบบ ad hoc เข้า database ที่รันจริงอยู่ และไม่มี environment ไหนแอบ drift ออกจากสิ่งที่อยู่ใน version control
เช็คก่อน ship: --dry-run กับ db lint พิสูจน์จริง
หัวข้อที่มีชื่อว่า “เช็คก่อน ship: --dry-run กับ db lint พิสูจน์จริง”CI step ที่แค่รัน db push เปล่า ๆ ยังห่าง migration พังหนึ่งตัวจาก production database ที่พังอยู่ดี มีสองอย่างที่ควรอยู่ก่อนหน้ามัน ไม่ทำลายอะไรและจริงทั้งคู่ อย่างแรก --dry-run — list ว่าจะ apply อะไรบ้างโดยไม่แตะอะไรเลย:
$ supabase db push --db-url "postgresql://...127.0.0.1:55322/postgres?sslmode=disable" --dry-run --yesDRY RUN: migrations will *not* be pushed to the database.Would push these migrations: • 20260929070903_fnd_capture_courses.sql • 20260929072708_fnd_add_note_to_widgets.sql{"upToDate":false,"dryRun":true,"migrations":[...],"message":"Finished supabase db push."}ยืนยันกับ database จริงทีหลังแล้วว่า — migration ที่ list ไว้ ไม่ได้ ถูก apply เลย column ที่หนึ่งในนั้นเพิ่มเข้าไปยังไม่มีอยู่ คู่บทใน Foundations พิสูจน์ migration workflow เต็มรูปแบบ (migration new, db diff -f, db push, migration list) และ output ของ db lint แบบครบวงจรกับ local stack เดียวกันนี้แล้ว — บทนี้ไม่พิสูจน์ซ้ำ แค่เติมส่วนที่เป็น shape ของ CI เพิ่มเข้าไป: step --dry-run และ step db lint --fail-on error ทั้งคู่รัน ก่อน db push จริงใน pipeline เดียวกัน เพื่อให้ migration ที่พังหรือใหญ่เกินคาดถูกจับได้ใน CI output แทนที่จะจับได้จากสิ่งที่ migration นั้นทำกับ production จริง ๆ
supabase db push --db-url "$SUPABASE_DB_URL" --dry-runsupabase db lint --db-url "$SUPABASE_DB_URL" --fail-on errorsupabase db push --db-url "$SUPABASE_DB_URL"flowchart LR
pr["Git branch / pull request"] --> branch["Supabase preview branch (isolated schema copy)"]
branch --> mig["Migrations applied and tested on the branch"]
mig --> merge["Pull request merged"]
merge --> ci["CI runs supabase db push"]
ci --> prod[("Production Postgres")] อ่านโค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI สร้าง”AI assistant ถูกขอให้ “set up the CI step that deploys database changes” แล้วสร้างสิ่งนี้:
🐛 หา bug ในโค้ดนี้
# An AI assistant's suggested CI step for shipping migrationssupabase link --project-ref "$SUPABASE_PROJECT_REF"supabase db push --yesเฉลย
สิ่งนี้รันโดยไม่ถามอะไรเลย ซึ่งใน CI pipeline คือ property ที่ผิดที่สุดสำหรับ step ที่ไม่มีการเช็คมาก่อน flag --yes ข้าม confirmation แบบ interactive “do you want to push these migrations?” ที่ตัวอย่างก่อนหน้าในบทนี้แสดงเป็น default — สมเหตุสมผลสำหรับ pipeline แบบ non-interactive แต่ต้องมีบางอย่างเช็คก่อนแล้วว่าสิ่งที่กำลังจะ ship คืออะไร ตรงนี้ไม่มีเลย: ไม่มี --dry-run เพื่อ print ว่าจะเกิดอะไรขึ้น ไม่มี db lint เพื่อจับ typing error ก่อนที่มันจะไปถึง production และไม่มีการแยกแยะระหว่าง migration ที่เคยรันปลอดภัยบน preview branch แล้ว กับตัวที่ไม่เคย test เลย ทางแก้คือ pipeline สามบรรทัดที่พิสูจน์ไว้ข้างบน: --dry-run ก่อน (print อย่างเดียว ไม่ apply) db lint --fail-on error ต่อ (ทำให้ build fail ถ้ามี schema error จริงก่อนที่จะแตะ database) แล้วค่อย db push จริง — เปลี่ยนจาก “เชื่อ CLI แล้วหวังว่าจะรอด” เป็น “verify แล้วค่อย apply” การเปลี่ยนจาก ClickOps เป็น process ที่ review ได้แบบเดียวกับที่ module นี้ทั้งหมดวนกลับมาย้ำตลอด
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ปฏิบัติกับ step db push ของ CI เหมือนกับที่คุณปฏิบัติกับการ deploy application code: ต้องมีอะไรบางอย่าง verify change ก่อน ที่มันจะรันกับของจริง และการเช็คนั้นควร fail แบบดัง ๆ แล้วหยุด pipeline แทนที่จะปล่อย migration พังผ่านไปเพราะ “ปกติมันก็ใช้ได้” --dry-run กับ db lint คือ verification step สำหรับ schema change โดยเฉพาะ — ถูก ไม่ทำลายอะไร และทั้งคู่พิสูจน์ข้างบนแล้วว่ารันกับ database จริงตัวเดียวกับที่ push จริงจะเล็งไป ไม่ใช่ simulation ของมัน
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”- Branching ให้ database ที่แยกเต็มรูปแบบสำหรับ test migration เสี่ยง ๆ — แลกมาด้วยต้องมี hosted project ที่ link ไว้ (พิสูจน์ตลอดคอร์สนี้: local CLI stack เดี่ยว ๆ จำลอง branching เองไม่ได้ เพราะไม่มีแนวคิดของ hosted preview environment)
--dry-runเร็วและปลอดภัยเต็มที่ที่จะรันทุก CI run — แลกมาด้วยเช็คแค่ “จะ apply อะไร” ไม่ใช่ “สิ่งนี้จะสำเร็จจริงไหม” — migration ที่ผ่าน dry-run ก็ยังพังตอนdb pushจริงได้ ถ้าเช่นว่า table ที่มัน depend อยู่ถูกลบไปตั้งแต่ตอนเขียน migration file- การ automate
db pushผ่าน CI ตัด human error ของ ad hoc production SQL ออกไป — แลกมาด้วยทำให้ CI pipeline เองกลายเป็น target ที่ต้อง trust สูง: credential ไหนก็ตามที่ให้ CI รันdb pushเข้า production ได้ ทำอะไรก็ได้เหมือน migration file ไหนก็ทำได้ ซึ่งเป็นเหตุผลตรงที่ credential พวกนั้นควรได้รับความระมัดระวังเท่า service_role key ที่อื่นในคอร์สนี้
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “
--dry-runtest จริง ๆ ว่า migration จะสำเร็จไหม” ถูกหักล้างด้วยสิ่งที่มันทำจริงข้างบน: มัน print แค่ว่า migration file ไหน pending อยู่ โดยไม่เชื่อมกับ Postgres เพื่อรันจริง —db lintกับdb pushจริงในที่สุดต่างหากที่รัน SQL จริง - “
--yesเป็นแค่ shortcut ที่ข้ามอะไรที่ optional” มันข้าม confirmation แบบ interactive หนึ่งตัวที่กั้นระหว่าง CI pipeline กับการ apply migration โดยไม่มี human review เลย — เหมาะแค่ตอนที่--dry-run/db lintทำหน้าที่นั้นแบบ programmatic ไปแล้วเท่านั้น อย่างที่ SpotTheBug ข้างบนแสดงว่าพังยังไงถ้าไม่มี - “Branching กับ migration เป็น feature ของ CLI สองอันที่ไม่เกี่ยวกัน บังเอิญอยู่ module เดียวกัน” มันคือ workflow เดียวกันคนละขั้นตอน: migration ถูก test แบบแยกส่วนบน branch แล้วไฟล์เดิมเป๊ะ ๆ — ไม่เปลี่ยน — คือสิ่งที่ CI apply เข้า production branching ที่ไม่มี migration ให้ test ก็จะไม่มีอะไรที่ replay ต่อไปได้