Putting a Database Schema in Git
For backend developers who use Git for application code and want their database schema versioned the same way.
On this page
Your application code goes through branches and pull requests, but the database still changes through scripts someone ran by hand. Database schema version control fixes that with one rule. Keep a text file that describes the schema in the repository, and change the database only from that file. In DbSchema, that file is the .dbs model, and Git is built in.
In short
Put a text description of the schema in Git and treat it as the source of truth. Review every schema change as a diff in a pull request. Apply the change to the database in a separate, visible step.
Table of contents
- Why a database schema belongs in Git
- Local, centralized and distributed version control
- Two ways to version a schema: state and migrations
- What a DbSchema model file looks like in Git
- Branches and merge conflicts in a model file
- How a change in Git reaches the database
- Habits that keep a schema repository easy to merge
- Frequently asked questions
- Sources
Key takeaways
- A schema in Git needs a text format, because Git diffs and merges lines of text.
- DbSchema saves the whole design in one .dbs model file, which is plain XML.
- Git changes only the file. Schema synchronization writes the change into the database and shows the SQL first.
- A merge conflict in a model file looks like any other conflict, with the usual markers.
Why a database schema belongs in Git
A schema that lives only in the database has no history. You can't see who added a column, why an index exists, or which version runs in staging. When production differs from your laptop, nothing tells you which one is right.
Git answers those questions for code, and it can answer them for the schema too. The schema just has to be a file that Git can read line by line. Then a schema change gets a branch, a commit message and a reviewer, like any other change. The case for doing this at all is in why version a schema at all.
Local, centralized and distributed version control
Version control systems come in three kinds: local, centralized and distributed. The Pro Git book[1] has a section on each.
| Kind | Where the history lives | Examples |
|---|---|---|
| Local | Copies of files on one machine, often in dated folders | Manual copies |
| Centralized | One server holds every versioned file, clients check files out | CVS, Subversion, Perforce |
| Distributed | Every clone mirrors the whole repository and its history | Git, Mercurial |
Local copies are simple, but Pro Git calls the method "incredibly error prone". A centralized server makes the server a single point of failure. In a distributed system, every developer has the full history, so you can branch, diff and commit offline.
Git is the most common of them. In the Stack Overflow Developer Survey 2022[2], 93.87% of 71,379 respondents used Git. That is why the rest of this article assumes Git.
Two ways to version a schema: state and migrations
You can store a schema in Git in two ways. Each puts a different file in the repository.
| State-based | Migration-based | |
|---|---|---|
| What Git holds | The full schema as it should be | A numbered series of change scripts |
| What a diff shows | The schema before and after | One new script |
| How the database changes | A compare step generates the SQL | The scripts run in order |
State-based tracking keeps the target schema, and a compare step works out the SQL. Migration-based tracking keeps every step, and the database replays them. The state approach is easier to review, because the diff shows the schema itself. If you want migrations without a framework, see schema versioning without a migration framework.
DbSchema works state-based. The model file is the target, and the SQL comes from comparing it with a database.
What a DbSchema model file looks like in Git
DbSchema saves your whole design, tables, foreign keys and diagrams, in one model file with the extension .dbs. Saving the model to a file needs DbSchema Pro. The file is XML, so Git can diff it.
We checked this on 25 October 2026 with the model file DbSchema saved for our 14-table PostgreSQL demo schema. file reports it as plain XML, 28,621 bytes. Each column is one line:
<table name="projects" row_count="0" spec="" >
<column name="description" type="text" />
<column name="is_archived" type="boolean" mandatory="y" >
<defo><![CDATA[false]]></defo>
</column>
Then we put the file in a Git repository and added an archived_at column and an index to projects on a branch. We wrote the lines by hand, in the format the file already uses. git diff shows four added lines:
@@ -128,6 +128,7 @@
<column name="is_archived" type="boolean" mandatory="y" >
<defo><![CDATA[false]]></defo>
</column>
+ <column name="archived_at" type="timestamptz" />
<column name="created_by" type="integer" />
@@ -138,6 +139,9 @@
<index name="projects_pkey" unique="PRIMARY_KEY" >
<column name="id" />
</index>
+ <index name="idx_projects_archived_at" unique="NORMAL" spec="USING btree" >
+ <column name="archived_at" />
+ </index>
A reviewer can read that without opening any database tool. The column type, the nullability and the index are all in the diff. For the review itself, see reviewing a schema change in a pull request.
A binary model format can't do this. MySQL bug 83076 on Oracle's bug tracker states that "merging .mwb files under version control does not work, as the files are binary." Those are MySQL Workbench model files.
Branches and merge conflicts in a model file
DbSchema has Git built in, so you need no other Git program. Choose File → Git Collaboration to open the Git dialog. From there you commit, push, pull and create a branch. Model History lists every commit that changed the model, with the tables each commit touched.
File → Git Collaboration lists every commit of the model file. Saving the model to a file needs DbSchema Pro.
Conflicts happen when two people change the same part of the file. We tested it: one branch added archived_at to projects, a second branch added owner_id at the same spot. Merging both into main stopped with CONFLICT (content): Merge conflict in dbschema_demo.dbs. Git wrote the standard conflict markers[3] into the model file:
<<<<<<< HEAD
<column name="archived_at" type="timestamptz" />
=======
<column name="owner_id" type="integer" />
>>>>>>> add-owner
You resolve it like any text conflict. In DbSchema, the conflicted file shows in red in the Git dialog. Ours keeps your version, Theirs keeps your teammate's version, and Merge... opens both side by side so you take part of each. Here you would keep both lines.
How a change in Git reaches the database
Git changes only the model file. The database doesn't know about your commit. To bring the change into a database, you synchronize the model with the database. Synchronization needs DbSchema Pro.
DbSchema compares the model with the database and lists every difference. For each one, you choose whether the model or the database wins. DbSchema then writes the SQL for the database side and shows it before it runs. You can also press Generate SQL script to get the statements without running them.
For the archived_at change, the database needs two statements. We ran them on a copy of the demo database, on PostgreSQL 16.14:
ALTER TABLE public.projects ADD COLUMN archived_at timestamptz;
CREATE INDEX idx_projects_archived_at ON public.projects USING btree (archived_at);
Both succeeded, and the projects table had the new nullable column:
| column_name | data_type | is_nullable |
|---|---|---|
| is_archived | boolean | NO |
| archived_at | timestamp with time zone | YES |
The new column has no default. The PostgreSQL 16 documentation[4] says NULL is then used, and no table rewrite is needed. A column with a volatile default would be a different, slower change.
You can also compare two versions of the model with each other. A second .dbs file can hold an older version of the schema. Check out the old commit's file, run Compare Model with Other Model From File, and use Generate SQL script. Nothing runs in a database in that case. More on turning a diff into a script is in turning a schema diff into a safe migration script.
Habits that keep a schema repository easy to merge
A few habits keep conflicts small and history useful:
- Keep each database in its own model file. Several model files can share one repository.
- Commit one change at a time, with a message that says what changed, such as "Add archived_at to projects".
- Pull often, so your branch never drifts far from the team's.
- Review the model diff in a pull request before anyone synchronizes a shared database.
- Synchronize from the merged file only. A change typed straight into production skips the review.
The first three come from the DbSchema Git documentation.
Download DbSchema and reverse-engineer your database into a model. Save it with DbSchema Pro into your repository, then open File → Git Collaboration to make your first commit. Pro costs $294.00 one-time or $29.40 per month, and it covers saving the model to a file and schema synchronization.
Frequently asked questions
Is there a free version of MySQL Workbench?
Yes, MySQL Workbench is free. Its .mwb model files are binary, though, so Git can't merge them.
Can I keep the SQL migration scripts in Git as well?
Yes. Generate SQL script writes the statements to a file, and you can commit that file next to the model.
Does a Git pull change my database?
No. A pull changes only the model file. The database changes when you synchronize.
Sources
Put the schema in Git as reviewable text
DbSchema saves the whole design as one indented XML .dbs file, so a schema change reads as a line-by-line diff in a pull request. Saving the design to a file and schema synchronization are Pro Edition features.