# Gitea Database Migration Strategies: Version-Driven Schema Management in Go

> Discover Gitea's database migration strategies. Learn how Gitea uses version-driven schema management and atomic Go functions for seamless database upgrades.

- Repository: [Gitea/gitea](https://github.com/go-gitea/gitea)
- Tags: database-migration
- Published: 2026-03-07

---

**Gitea employs a sequential, version-driven migration system that tracks schema state in a single `version` table and executes atomic Go functions to migrate the database forward from any previous version.**

The `go-gitea/gitea` project implements a robust, code-first approach to schema evolution. Unlike external SQL script-based tools, Gitea's database migration strategies leverage compiled Go functions and the XORM ORM to ensure type-safe, atomic schema changes that can resume safely if interrupted.

## Core Architecture of Gitea's Migration System

### The Version Table and State Tracking

At the heart of Gitea's migration strategy lies a singleton `version` table that stores the current database schema version. According to the source code in [`models/migrations/migrations.go`](https://github.com/go-gitea/gitea/blob/main/models/migrations/migrations.go), the system synchronizes this table with the `Version` struct defined at lines 66-71. This single-row table contains a `Version` field representing the integer ID of the last successfully applied migration.

When the application starts, the migration engine compares this stored value against the list of available migrations defined in the codebase. This design ensures that Gitea always knows exactly which schema changes have been applied, even if previous migration attempts were interrupted.

### Migration Registration and Structure

Each database migration is defined as a `migration` struct containing three critical fields: an integer ID, a human-readable description, and a `migrate` function pointer. The `prepareMigrationTasks()` function assembles these into a sequential slice.

The system enforces a minimum supported version through the `minDBVersion` constant (set to 70), which protects against attempts to upgrade from unsupported ancient releases. As implemented in [`models/migrations/migrations.go`](https://github.com/go-gitea/gitea/blob/main/models/migrations/migrations.go), the `calcDBVersion()` function validates that the migration list starts at `minDBVersion` and that the final ID matches the expected database version, ensuring the codebase and database remain in sync.

## How Gitea Executes Database Migrations

### The Sequential Execution Flow

The `Migrate` function—invoked by the `gitea migrate` CLI command— orchestrates the entire process. Located in [`models/migrations/migrations.go`](https://github.com/go-gitea/gitea/blob/main/models/migrations/migrations.go), this function performs the following steps:

1. Synchronizes the `Version` model with the database
2. Determines the current version or creates a fresh record for new installations
3. Verifies the version is within supported bounds (respecting `minDBVersion`)
4. Initializes the Git subsystem via `git.InitSimple`
5. Iterates over pending migrations determined by `getPendingMigrations()`

The `getPendingMigrations()` function slices the migration array based on the current database version, ensuring only newer migrations execute. After each migration completes successfully, the system updates the `version` table row, making each step atomic and resumable.

### Version Validation and Safety Guards

Gitea implements strict safety checks to prevent data corruption. The `EnsureUpToDate` function (referenced in [`models/migrations/migrations.go`](https://github.com/go-gitea/gitea/blob/main/models/migrations/migrations.go)) allows runtime code to verify that the database matches the expected version before proceeding with application logic. If a version mismatch is detected, Gitea instructs operators to run the migration command rather than attempting automatic schema changes during runtime.

## Migration Implementation Patterns

### Version-Specific Migration Packages

Individual migrations live in version-specific packages following the `models/migrations/v1_XX/` directory structure (e.g., [`models/migrations/v1_10/v90.go`](https://github.com/go-gitea/gitea/blob/main/models/migrations/v1_10/v90.go)). These implementations are simple Go functions that receive a `*xorm.Engine` parameter, allowing them to either use XORM's `Sync` method for table/column adjustments or execute raw SQL statements when necessary.

For example, adding a new migration for version 1.27 (ID 326) follows this pattern:

```go
// models/migrations/v1_27/v326.go
package v1_27

import "xorm.io/xorm"

// AddIssueLabelsTable creates a new table for issue labels.
func AddIssueLabelsTable(x *xorm.Engine) error {
    type IssueLabel struct {
        ID      int64  `xorm:"pk autoincr"`
        IssueID int64  `xorm:"INDEX"`
        LabelID int64  `xorm:"INDEX"`
    }
    return x.Sync(new(IssueLabel))
}

```

Registration occurs within `prepareMigrationTasks()` in the main migrations file:

```go
// Inside prepareMigrationTasks() in models/migrations/migrations.go
newMigration(326, "add issue_labels table", v1_27.AddIssueLabelsTable),

```

### No-Op Migrations for Stability

When a migration becomes obsolete or is superseded by later changes, Gitea does not remove it from the sequence. Instead, the system inserts a `noopMigration` stub to maintain stable IDs. This approach ensures that migration numbers remain consistent across releases and that historical database states can always be understood by examining the code.

## Running Migrations in Production

Database migrations are executed via the Gitea CLI rather than automatic startup processes. Operators run:

```bash

# From a terminal on a deployed Gitea instance

gitea migrate

# Output will show each step, e.g.:

# Migration[326]: add issue_labels table

```

This command entry point resides in [`commands/migrate.go`](https://github.com/go-gitea/gitea/blob/main/commands/migrate.go), which delegates to the `Migrate` function in the models package. By separating migration execution from application startup, Gitea allows administrators to control when schema changes occur and to back up databases before potentially destructive operations.

## Summary

- **Version-controlled state**: Gitea tracks schema evolution using a monotonic integer stored in a single-row `version` table.
- **Sequential and additive**: Every schema change appends a new migration to the list; historical migrations are never removed, only converted to no-ops when superseded.
- **Code-first approach**: Migrations are compiled Go functions using XORM, providing type safety and access to application logic rather than static SQL files.
- **Atomic execution**: Each migration updates the version row independently, allowing the process to resume safely if interrupted.
- **Safety boundaries**: The `minDBVersion` constant and `EnsureUpToDate` checks prevent incompatible upgrades and runtime version mismatches.

## Frequently Asked Questions

### How does Gitea track which migrations have already run?

Gitea maintains a single-row `version` table in the database containing a `Version` field. When migrations execute, the system compares this stored integer against the migration list defined in [`models/migrations/migrations.go`](https://github.com/go-gitea/gitea/blob/main/models/migrations/migrations.go) and executes only those with higher IDs via `getPendingMigrations()`.

### What happens if a Gitea migration fails halfway through?

Because Gitea updates the `version` table row immediately after each individual migration succeeds, the process is resumable. If a failure occurs during migration 150, the database remains at version 149. Running `gitea migrate` again will skip completed migrations and resume from the failed step.

### Can I downgrade Gitea to a previous database version?

No. Gitea's database migration strategies are designed as forward-only operations. The system does not implement rollback mechanisms or down-migrations. To revert to a previous version, you must restore from a database backup taken before the upgrade.

### Why does Gitea use Go functions instead of SQL files for migrations?

Using Go functions provides compile-time safety, access to the XORM ORM for database-agnostic schema changes, and the ability to reuse application logic and constants. This approach prevents syntax errors that might only surface at runtime with raw SQL scripts and ensures migrations remain synchronized with the codebase structure.