Migrări Supabase: schema ca fișier în Git
O schimbare de schemă aparține unui fișier pe care fiecare mediu îl aplică la fel. Vezi cum creezi, aplici, resetezi și revizuiești migrări Supabase și de ce o schimbare din Studio nu este încă o migrare.

O schimbare de schemă ar trebui să fie un fișier în repository, nu o suită de clicuri într-un panou. În Supabase, fișierul acela stă în supabase/migrations, iar același fișier se aplică pe laptop, pe mașina unui coleg și în producție. Baza de date capătă astfel un istoric pe care îl poți citi, revizui și reconstrui. Istoricul este ce face o schimbare de schemă verificabilă și ce desparte o bază de date pe care o poți reproduce de una pe care nu îndrăznești să o atingi.
Ce este o migrare în Supabase?
O migrare este un fișier SQL care creează, modifică sau șterge o parte din schema bazei de date. Ghidul Supabase despre migrări le descrie drept înregistrarea schimbărilor de schemă în timp, păstrată în supabase/migrations. Fiecare fișier urmează modelul <timestamp>_<nume>.sql, de exemplu 20230306095710_schema_test.sql, iar CLI-ul le aplică în ordinea timestamp-urilor. Datele de seed sunt separate: stau în supabase/seed.sql, nu într-o migrare.
Separarea nu este decorativă. O migrare descrie structura, iar toate mediile trebuie să ajungă la aceeași structură. Datele de seed sunt un ajutor pentru lucrul local și pentru teste. Dacă pui rânduri într-o migrare, fiecare push le cară în producție, inclusiv pe cele pe care le voiai doar pe mașina ta.
Cum creezi o migrare?
Când știi ce SQL vrei, creezi o migrare goală și o scrii:
supabase migration new add_department_column
Referința CLI spune că această comandă creează fișierul local, creează directorul supabase/migrations dacă nu există și afișează calea, de exemplu supabase/migrations/20230306095710_schema_test.sql. Fișierul pornește gol, deci SQL-ul îl scrii și îl revizuiești tu.
Când schimbarea a apărut din clicuri în Dashboard-ul local, lasă CLI-ul să scrie SQL-ul:
supabase db diff -f create_cities_table
Ghidul de migrări dă exact acest exemplu. db diff compară baza de date locală cu o bază de date umbră construită prin aplicarea migrărilor într-un container separat, iar -f salvează diferența ca fișier de migrare nou, în loc să o afișeze. Aici se leagă experimentul din interfață de fișierul din repository. Interfața este un loc bun pentru încercat; este un loc prost pentru lăsat.
Cum aduci baza de date locală la zi?
Supabase local are nevoie de un runtime de containere. Ghidul de dezvoltare locală enumeră Docker Desktop, OrbStack, Rancher Desktop și Podman și spune că un API compatibil Docker este o condiție pentru supabase start. Un checkout nou înseamnă două comenzi, supabase init și supabase start, cu dashboard-ul local la http://localhost:54323.
Apoi aplici ce nu a văzut încă baza de date:
supabase migration up --local
Referința CLI descrie opțiunea ca aplicând migrările în așteptare pe baza de date locală. Ca să vezi unde ești, rulezi supabase migration list --local. Pe un proiect legat, aceeași comandă compară fișierele locale cu istoricul remote, pe care referința îl plasează în supabase_migrations.schema_migrations. Se compară doar timestamp-urile: nu numele, nu conținutul. Ține minte asta, pentru că explică eșecul din secțiunile următoare.
Care este diferența dintre aplicare și resetare?
Aplicarea rulează migrările pe care baza de date nu le-a văzut încă și lasă datele existente în pace. Resetarea aruncă baza de date și o reconstruiește. supabase db reset recreează containerul Postgres local, aplică toate migrările din supabase/migrations de la prima, apoi rulează supabase/seed.sql dacă există. Referința CLI adaugă partea pe care lumea o uită: orice altă modificare de date sau de schemă făcută în timpul lucrului local este aruncată.
Noi resetăm des, intenționat. Resetarea este testul ieftin al afirmației că migrările pot reconstrui baza de date de la zero. Aplicarea testează pasul următor; resetarea testează tot istoricul. Dacă resetarea eșuează pe o mașină curată, un mediu nou sau o recuperare vor eșua la fel, doar într-un moment mai prost.
Resetarea are și o formă distructivă. Cu --linked sau --db-url, comanda rulează SQL care șterge toate entitățile create de utilizatori din baza de date remote. Trateaz-o ca pe o comandă de producție și ține resetările local, dacă nu ai un backup și un motiv foarte bun.
Cum ajung migrările în producție?
Legi proiectul o dată, apoi faci push:
supabase link --project-ref <ref>
supabase db push
db push trimite toate migrările locale către baza de date remote. Prima rulare creează supabase_migrations.schema_migrations; push-urile următoare sar peste migrările deja înregistrate acolo. --dry-run afișează migrările care ar fi aplicate fără să le aplice, iar --include-seed populează și baza remote cu datele de seed. Referința notează că push și pull cer un proiect legat.
De ce o schimbare făcută în Studio nu este o migrare?
Pentru că nu s-a scris nimic într-un fișier sau în tabelul de istoric. Pe un stack local poți schimba un tabel din Dashboard și apoi poți captura schimbarea cu db diff. Pe un proiect găzduit, ghidul de migrări este direct: modificările făcute prin SQL editor sau Table Editor ocolesc istoricul migrărilor și vor face db push să eșueze cu erori de sincronizare. Regula ghidului este că toate schimbările de schemă pe baza remote trebuie să treacă doar prin fișiere de migrare.
Consecința este reproducerea. O coloană adăugată din Table Editor pe remote există în producție și nicăieri altundeva. Un stack local pornit de la repository nu o are, un coleg nu o vede într-un diff, iar următorul db reset produce o bază de date care diferă de producție fără nicio urmă despre când a apărut diferența. Exact această situație există ca să fie prevenită de migrări.
Dacă schimbarea a avut loc deja pe remote, salvarea documentată este supabase db pull. Referința spune că aduce schimbările de schemă dintr-o bază de date remote și le scrie ca migrare nouă în supabase/migrations, iar un rând nou poate fi inserat în tabelul de istoric pentru a reflecta starea remote. Fă-o cât timp schimbarea e proaspătă și îți mai amintești de ce există.
Ce se strică atunci când editezi o migrare care a rulat deja?
Tabelul de istoric ține timestamp-uri, nu sume de control. Odată ce producția are un rând pentru o migrare, db push o consideră făcută și o sare. Dacă editezi fișierul după aceea, producția nu o rulează din nou. O bază de date locală nouă, sau un coleg care nu a aplicat niciodată versiunea aceea, rulează însă fișierul editat. Două medii au acum scheme diferite construite din același istoric, iar nicio comandă nu se plânge.
Se întâmplă cel mai des la o corectură mică. Cineva aplică o migrare, observă un index lipsă sau o valoare implicită greșită și editează fișierul în loc să adauge o a doua migrare, pentru că editarea pare mai curată. Curățenia este locală. Fișierul care a produs producția nu mai este fișierul din repository, iar istoricul nu mai descrie ce a rulat de fapt.
Obiceiul mai sigur este să adaugi o migrare nouă care schimbă ce a făcut cea veche:
supabase migration new fix_department_column_type
Istoricul rămâne numai cu adăugiri, fiecare mediu aplică aceeași secvență, iar corectura se vede în review. Când singura problemă este evidența, supabase migration repair poate marca o versiune ca aplicată sau revenită, dar referința spune că actualizează doar tabelul de urmărire. Nu rulează și nu anulează niciun SQL, deci nu face să dispară o diferență reală de schemă.
Există și supabase migration squash, care combină migrările locale într-un singur fișier și e util pentru a scoate modificările repetate dintr-un istoric lung. Citește-i pagina înainte: instrucțiunile de manipulare a datelor, precum insert, update sau delete, sunt omise din migrarea comprimată, iar cron job-urile, bucket-urile de storage și secretele din vault trebuie adăugate din nou manual. Squash pune ordine într-un istoric; nu repară o divergență.
De ce citești SQL-ul generat înainte să ruleze?
db diff scrie implicit la stdout și creează un fișier doar când dai -f. În ambele cazuri, SQL-ul este rezultatul care contează. Citește-l cum citești cod: aruncă o coloană, schimbă un tip, redenumește un tabel, adaugă sau scoate o politică? Un diff este o propunere, iar unele propuneri pierd date. O migrare care rulează fără erori poate fi totuși migrarea greșită.
Iată ce urmărim într-un diff generat:
- Instrucțiuni distructive precum
drop columnșidrop table, plus schimbări de tip care rescriu sau trunchiază valori. - Valori implicite și obligativitate pe coloane care au deja rânduri, pentru că adăugarea unei coloane obligatorii într-un tabel populat poate eșua sau poate umple date imprevizibil, în funcție de instrucțiune.
- Politici row-level security și permisiuni, pe care un diff de schemă le poate rata sau le poate reprezenta altfel decât intenționai.
- Obiecte la care nu te așteptai, precum funcții, triggere sau indexuri create de o extensie.
Review-ul e ieftin pentru că migrarea este un fișier text într-un pull request. Recenzentul vede instrucțiunea exactă care va rula în producție, nu un rezumat al ei. Înainte de push, supabase db push --dry-run listează ce se va aplica. Două obiceiuri acoperă cea mai mare parte a riscului: citește diff-ul înainte să îl comiți și citește dry run-ul înainte să faci push. Articolul nostru despre conținutul în Git face același argument pentru text; o schemă de bază de date merită același tratament.
Unde se leagă asta de munca noastră
Dacă rulezi mai multe stack-uri locale, porturile implicite ale CLI-ului permit un singur proiect local pe mașină, după cum notează ghidul de dezvoltare locală. Supabase Local Manager este un proiect independent, în early access și neafiliat cu Supabase, Inc., care rulează mai multe stack-uri Supabase locale dintr-un singur serviciu Node și Express. Zona lui de migrări listează, creează, generează diff-uri și aplică migrări pentru fiecare proiect, păstrând intact fluxul bazat pe fișiere descris mai sus.
Danube Labs construiește și întreține sisteme în care schema supraviețuiește funcției care a introdus-o. Dacă vrei o a doua părere despre felul în care schimbările bazei tale de date ajung în producție, vezi lucrările noastre de software custom sau programează un apel introductiv gratuit.
Întrebări frecvente
- Ce este o migrare Supabase?
- O migrare este un fișier SQL în supabase/migrations care descrie o schimbare de schemă, de exemplu crearea unui tabel sau adăugarea unei coloane. Supabase aplică fișierele în ordinea timestamp-urilor și le înregistrează în supabase_migrations.schema_migrations. Fiecare mediu rulează aceleași fișiere, deci baza de date poate fi reconstruită din repository.
- Care este diferența dintre supabase migration up și supabase db push?
- migration up aplică migrările în așteptare pe baza de date locală cu --local sau pe proiectul legat cu --linked. db push trimite toate migrările locale către baza de date remote, creează tabelul de istoric la prima rulare și sare peste migrările deja înregistrate acolo. Folosești push când lucrul local este gata să ajungă în producție.
- Ce face supabase db reset?
- Recreează containerul Postgres local și aplică toate migrările din supabase/migrations de la început, apoi rulează supabase/seed.sql dacă fișierul există. Orice altă modificare de date sau de schemă făcută în timpul lucrului local este aruncată. Cu --linked sau --db-url rulează un script distructiv pe baza de date remote, care șterge toate entitățile create de utilizatori.
- De ce eșuează db push după o schimbare în Dashboard?
- Modificările făcute prin SQL editor sau Table Editor pe proiectul găzduit ocolesc istoricul migrărilor, deci baza de date remote nu mai corespunde cu ce descriu fișierele locale. Ghidul Supabase spune că db push eșuează apoi cu erori de sincronizare. Adu schema remote într-o migrare cu supabase db pull, iar de acum fă schimbările remote doar prin fișiere de migrare.
- Pot edita o migrare care a fost deja aplicată?
- Nu este sigur. Tabelul de istoric ține timestamp-uri, deci un mediu care a aplicat deja versiunea aceea va sări peste fișierul editat, în timp ce o bază de date nouă îl va rula. Cele două scheme diferă apoi în tăcere. Adaugă mai bine o migrare nouă care o corectează pe cea veche și lasă fișierul aplicat neatins.
- Unde sunt stocate migrările Supabase?
- Stau în directorul supabase/migrations al proiectului, un fișier pentru fiecare schimbare, numit cu un timestamp și o descriere scurtă, de exemplu 20230306095710_schema_test.sql. Datele de seed sunt păstrate separat în supabase/seed.sql. Ambele aparțin controlului de versiuni, alături de restul codului.


