A practical walkthrough from zero to production.
- Installation
- Setting up a project
- Creating migrations
- Running migrations
- Rolling back
- Multi-database setup
- CI/CD integration
- Approval gates and deployment windows
- Troubleshooting
Prerequisite: None for the binary install methods. For the .NET tool method, you need .NET 8 SDK.
# Linux / macOS
curl -fsSL https://github.com/AzimMahmud/dbshift/releases/latest/download/install.sh | bash
# Windows (PowerShell)
powershell -c "iwr -Uri https://github.com/AzimMahmud/dbshift/releases/latest/download/install.ps1 | iex"These scripts detect your OS and architecture, download the correct binary, and add it to your PATH.
| Platform | Download |
|---|---|
| Windows x64 | dbshift-windows-x64.zip |
| Linux x64 | dbshift-linux-x64.tar.gz |
| macOS x64 | dbshift-macos-x64.tar.gz |
Extract and place dbshift (or dbshift.exe) anywhere on your PATH.
dotnet tool install --global DbShift
dbshift --versiongit clone <your-repo>
cd DbShift
.\publish.ps1 # Windows → dist\dbshift.exe
./publish.sh # Linux/macOS → dist/dbshift
./dist/dbshift --helpThe fastest way to start is a single command — no manual file creation needed:
# Interactive mode (prompts for name, provider, directory)
dbshift new
# Non-interactive (all flags provided)
dbshift new --name MyApp --provider postgresql
# Specify output directory
dbshift new --name MyApp --provider sqlserver --output ./my-database-project
# JSON mode for CI
dbshift new --name MyApp --jsonWhen called without flags, dbshift new enters interactive mode:
- Prompts for your project name (default:
MyApp) - Lets you select the database provider from a list
- Asks whether to use the current directory or a different one
- Detects existing projects and asks about overwriting files
This creates:
Database/
Config/
migration.json # global config (edit connection string)
environments/
local.json # dev defaults, approval off
development.json # CI/CD friendly
staging.json # gated environment
production.json # approval + deployment window
Migrations/
Schema/
.gitkeep
V001__Example_Users.sql # runnable example (provider-specific SQL)
Data/
.gitkeep
Patch/
.gitkeep
Rollback/
.gitkeep
U001__Example_Users.sql # example rollback
Templates/
schema_migration.sql # used by `dbshift create`
data_migration.sql
patch_migration.sql
rollback_migration.sql
repeatable_migration.sql
.github/workflows/
database-migration.yml # GitHub Actions CI pipeline
.gitignore
Options:
| Option | Short | Description |
|---|---|---|
--name |
-n |
Project name (default: MyApp) |
--output |
-o |
Output directory (default: current directory) |
--force |
-f |
Overwrite existing files |
--provider |
-p |
Global option: postgresql, sqlserver, mysql, sqlite |
--json |
Global option: machine-readable JSON output |
The example migration (V001__Example_Users.sql) is generated with correct SQL for your chosen provider — proper column types, defaults, and index syntax for PostgreSQL, SQL Server, MySQL, or SQLite.
After scaffolding, you only need to:
- Set your connection string (env var or edit
migration.json) - Run
dbshift create --name YourMigration --type schemato add your own scripts - Write your SQL
- Run
dbshift migrate -c "$DB_CONNECTION_STRING"
If you prefer to set things up by hand, DbShift expects this layout (paths are configurable in migration.json):
your-repo/
Database/
Config/
migration.json # global settings
environments/
local.json # per-environment overrides
production.json
Migrations/
Schema/ # V-prefix schema migrations
Data/ # V-prefix data migrations
Patch/ # V-prefix patch migrations
Rollback/ # U-prefix rollback scripts
Templates/ # used by `dbshift create`
Create Database/Config/migration.json:
{
"migration": {
"version": "1.0.0",
"database": {
"provider": "postgresql",
"connectionString": "${DB_CONNECTION_STRING}"
},
"scripts": {
"path": "./Database/Migrations"
},
"tracking": {
"schema": "public",
"tableName": "__migration_history"
},
"execution": {
"lockTimeoutSeconds": 300,
"commandTimeoutSeconds": 3600,
"batchSize": 10,
"stopOnFailure": true
},
"approval": {
"requireApproval": ["production"],
"approvers": ["admin@company.com"]
}
}
}Connection string resolution order:
--connection-stringCLI flagDB_CONNECTION_STRINGenvironment variableenvironments/<name>.json→database.connectionString(with${VAR}expansion)migration.json→database.connectionString(with${VAR}expansion)
Per-environment files let you override the connection string and policy for each target. All fields support ${VAR} expansion from environment variables, so secrets never need to be committed.
Database/Config/environments/local.json:
{
"name": "local",
"database": {
"connectionString": "${DB_CONNECTION_STRING}"
},
"migration": {
"requireApproval": false,
"lockTimeoutSeconds": 30,
"maxBatchSize": 10
}
}Database/Config/environments/production.json:
{
"name": "production",
"database": {
"connectionString": "${PROD_DB_CONNECTION_STRING}"
},
"migration": {
"requireApproval": true,
"lockTimeoutSeconds": 300,
"maxBatchSize": 5
},
"deploymentWindow": {
"enabled": true,
"startTime": "02:00",
"endTime": "06:00",
"allowedDays": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
}When you run dbshift migrate --environment production, the tool resolves the connection string from the production environment file first (step 3 above), falling back to the global migration.json if the environment file doesn't specify one.
Check your setup:
# Show configuration, paths, and available environments
dbshift info
# Show available environments only
dbshift info --jsonDbShift uses Flyway-style naming. All scripts go under Database/Migrations/:
Database/Migrations/Schema/V001__CreateUsersTable.sql
Database/Migrations/Schema/V002__AddEmailColumn.sql
Database/Migrations/Schema/V20260617120000__CreateOrders.sql
V= versioned- Version can be a sequence (
001,002) or a UTC timestamp (20260617120000) __separates the version from the name- The name is PascalCase, underscores allowed
Database/Migrations/Data/V003__SeedRoles.sql
These go in the Data/ subdirectory, which tells DbShift they're data/seed migrations.
Database/Migrations/Patch/V004__FixIndexCollation.sql
Patches are schema changes in a separate folder for organisational clarity.
Database/Migrations/Rollback/U001__Rollback_CreateUsersTable.sql
Database/Migrations/Rollback/U002__Rollback_AddEmailColumn.sql
U= undo- The version must match the forward migration's version (without the
Vprefix) - Rollback scripts are paired with their forward counterparts
Database/Migrations/Schema/R__RefreshUserView.sql
R= repeatable (re-runs every time the checksum changes)- No version number, so
R__is the entire prefix
The fastest way to create a migration:
# Schema migration with timestamp version
dbshift create --name CreateUsersTable --type schema --author jane
# Schema migration with sequence version (001, 002...)
dbshift create --name AddEmailColumn --type schema --sequence
# Data migration
dbshift create --name SeedRoles --type data --author jane
# Patch migration
dbshift create --name FixIndexes --type patch
# Rollback script
dbshift create --name CreateUsersTable --type rollback
# Repeatable script (R__ prefix, re-applied when checksum changes)
dbshift create --name RefreshUserView --type repeatable
# Specify output directory
dbshift create --name AddColumns --type schema --dir ./Database/Migrations/Schema
# Include metadata
dbshift create --name CreateOrders --type schema --author jane --description "Orders and order_items tables"This creates a file like Database/Migrations/Schema/V20260617120000__CreateOrders.sql with:
-- Migration: CreateOrders
-- Author: jane
-- Created: 2026-06-17
-- Description: Orders and order_items tables
-- TODO: Add your SQL migration hereYou can also create migration files manually. The only requirement is the filename follows the convention:
V<version>__<Name>.sql
Optional metadata headers in the SQL file:
-- Depends: V001__CreateUsersTable.sql, V002__CreateRoles.sql
-- Author: jane
-- Description: Creates the orders tableThe validate command checks these for correctness.
Before you run anything, validate:
dbshift validate
dbshift validate --environment local
dbshift validate --json # for CIValidation checks:
- Naming: every file must match the
V/R/U+ version +__+ name convention - Syntax: scripts must not be empty
- Duplicates: no two versioned scripts can have the same version
- Dependencies: every
-- Depends:reference must point to an existing file
See what would be applied before touching the database:
dbshift planThis works offline (no database needed). It shows:
- Which migrations are pending
- Their type (Schema/Data/Patch)
- Whether a rollback script is available
Before your first deploy, create the tracking tables:
dbshift init --connection-string "Host=localhost;Database=myapp;Username=postgres;Password=secret"This creates four tables in your database:
| Table | What it tracks |
|---|---|
__migration_history |
Every applied migration per environment |
__migration_lock |
Distributed lock preventing concurrent deploys |
__migration_audit |
Append-only audit trail |
__migration_release |
Coordinated release bundles |
If the tables already exist (from a previous run), init is idempotent — it won't change them.
# Interactive (asks for confirmation)
dbshift migrate --connection-string "Host=localhost;Database=myapp;Username=postgres;Password=secret"
# Non-interactive (for automation)
dbshift migrate --connection-string "$DB_CONNECTION_STRING" --yes
# Specify environment (uses per-environment config)
dbshift migrate --environment production --yes
# Override batch size
dbshift migrate --batch-size 5
# Bypass deployment window check
dbshift migrate --environment production --forceWhat happens during a deploy:
- Lock acquisition: acquires a row-level lock to prevent concurrent runs
- Plan computation: determines which migrations are pending
- Batch execution: applies migrations in batches (configurable)
- Status tracking: each migration is recorded in
__migration_history - Audit logging: every action logged in
__migration_audit - Lock release: releases the distributed lock
- Result reporting: shows what was applied, how long it took
If a migration fails:
- The error is recorded in
__migration_history - The transaction for that single script is rolled back
- If
stopOnFailureistrue(default), the deployment stops immediately - You can repair the failed migration and retry
dbshift status
dbshift status --environment production
dbshift status --json # machine-readable for CIShows a summary and detailed table of all migrations with their status:
- Completed (green): successfully applied
- Pending (yellow): waiting to be applied
- Failed (red): failed during execution
- RolledBack (violet): undone via rollback
- InProgress (blue): currently being applied
dbshift history
dbshift history --environment production --limit 50
dbshift history --jsonShows the audit log: who did what, when, and details.
If a migration fails (e.g. because of a syntax error), fix the SQL, then:
# Repair a specific failed migration
dbshift repair --version 005
# Repair all failed migrations at once
dbshift repairThis removes the failed record(s) from __migration_history, allowing the migration(s) to be retried. It does NOT undo any database changes the failed script may have made — you need to clean those up manually.
Each forward migration must have a paired rollback script:
Database/Migrations/Schema/V001__CreateUsersTable.sql
Database/Migrations/Rollback/U001__Rollback_CreateUsersTable.sql ← pairs with V001
The version must match (without the V/U prefix).
dbshift rollback --environment localThis rolls back the most recent completed migration using its U script.
dbshift rollback --environment production --count 3Rolls back the 3 most recent completed migrations, in reverse order.
dbshift rollback --environment production --version 003Rolls back the migration with version 003 (if it's completed).
dbshift rollback --environment production --yesSkips the confirmation prompt.
DbShift supports four databases. The only thing that changes is the connection string and the provider setting.
dbshift migrate -c "Host=localhost;Port=5432;Database=myapp;Username=postgres;Password=secret"Config:
{ "database": { "provider": "postgresql", "connectionString": "..." } }dbshift migrate -c "Server=localhost;Database=myapp;User Id=sa;Password=secret;TrustServerCertificate=True"Config:
{ "database": { "provider": "sqlserver", "connectionString": "..." } }dbshift migrate -c "Server=localhost;Database=myapp;User=root;Password=secret"Config:
{ "database": { "provider": "mysql", "connectionString": "..." } }dbshift migrate -c "Data Source=./myapp.db"Config:
{ "database": { "provider": "sqlite", "connectionString": "..." } }You can override the provider at runtime without changing config:
dbshift migrate -c "..." -p sqlserver| Concern | PostgreSQL | SQL Server | MySQL | SQLite |
|---|---|---|---|---|
| Bool type | BOOLEAN |
BIT |
TINYINT(1) |
INTEGER (0/1) |
| UUID type | UUID |
UNIQUEIDENTIFIER |
CHAR(36) |
TEXT |
| Timestamp func | NOW() |
GETUTCDATE() |
UTC_TIMESTAMP() |
C# DateTime (ISO string) |
| ID generation | gen_random_uuid() |
NEWID() |
C# Guid | C# Guid |
| JSON storage | TEXT |
NVARCHAR(MAX) |
LONGTEXT |
TEXT |
| Auto-index | Yes | Requires IF NOT EXISTS |
Yes | Uses CREATE INDEX IF NOT EXISTS |
Every command supports --json for machine-readable output:
# Validate
dbshift validate --json
# → { "success": true, "scriptsChecked": 16, "errors": [], "warnings": [] }
# Deploy
dbshift migrate --json
# → { "success": true, "applied": 3, "appliedMigrations": ["001", "002", "003"], ... }
# Status
dbshift status --json
# → { "success": true, "applied": 3, "pending": 1, "failed": 0, ... }Exit codes: 0 = success, 1 = error, 2 = parse error.
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4 with: { dotnet-version: '8.0.x' }
- run: dotnet build DbShift.sln -c Release
- run: dotnet test DbShift.sln -c Release --no-build
- run: dotnet run --project src/DbShift.CLI -- validate
deploy-dev:
needs: validate
if: github.ref == 'refs/heads/main'
steps:
- run: dotnet run --project src/DbShift.CLI -- migrate -e development -y- script: dotnet run --project src/DbShift.CLI -- validate
displayName: 'Validate migrations'
- script: dotnet run --project src/DbShift.CLI -- migrate -e development -y
displayName: 'Deploy to dev'- Run
validatefirst (fast, no database needed) - Use
--jsonto capture structured results - Use
--yesto skip interactive prompts - Store connection strings in secrets / variable groups
- Use
--in-memoryfor validation-only steps - Promote through environments (dev → qa → prod) with approvals at each gate
For environments with requireApproval: true:
# Interactive: prompts for approver identity
dbshift migrate --environment production
# Non-interactive: provide approver
dbshift migrate --environment production --approver jane@corp.com
# With --yes, requires --approver
dbshift migrate --environment production --approver jane@corp.com --yesIf you attempt to deploy to an approval-gated environment without providing an approver, the command fails with a clear message.
For environments with a configured deploymentWindow:
# Outside the window → blocked with a message
dbshift migrate --environment production
# → Error: Outside the configured deployment window. Current time 14:30 is outside 02:00-06:00.
# Override with --force
dbshift migrate --environment production --forceThe deployment window checks:
- Time range: current time must be between
startTimeandendTime - Allowed days: current day of week must be in
allowedDays - Override:
--forcebypasses both checks
Approval gating and deployment windows work together:
# Both checks must pass (or be overridden)
dbshift migrate --environment production --approver deploy-bot --force --yes[FAIL] Migration '005' failed: syntax error at or near "CREAT"
What happened: The SQL in V005__*.sql has a syntax error.
Fix:
- Fix the SQL in the file
- Revert any partial changes the failed script made to the database
- Run
dbshift repair --version 005(or justdbshift repairto fix all failures) to remove the failed record - Run
dbshift migrateagain
Error: Duplicate migration version '002' (V002__CreateOrders.sql conflicts with V002__AddEmailColumn.sql).
Fix: Rename one of the files to use a different version.
Error: Migration filename 'something.sql' is invalid.
Expected format '<prefix><version>__<name>.sql'
Fix: Rename the file to follow the V001__Name.sql convention.
Failed to connect to database: Connection refused
Check:
- Is the database server running?
- Is the connection string correct?
- Is the host/port accessible?
- Does the provider match the database? (
-p sqlserverfor SQL Server, etc.)
Could not acquire migration lock for environment 'production'. Another deployment may be in progress.
Check:
- Is another CI job running
dbshift migrateat the same time? - Wait for it to finish, or run
dbshift repairto clear the failed state
Migration configuration not found at 'C:\projects\app\Database\Config\migration.json'.
Run the CLI from the repository root or pass --config.
Fix: Run dbshift from the repository root, or use --config <path>.
If you see spinner text or markdown mixed in with JSON output, you may be using an older version. Since v1.0.0, --json suppresses all decorative output. Run dbshift validate --json and verify the output is pure JSON.
| Issue | PostgreSQL | SQL Server | MySQL | SQLite |
|---|---|---|---|---|
| Auto-increment | SERIAL / BIGSERIAL |
IDENTITY(1,1) |
AUTO_INCREMENT |
AUTOINCREMENT |
| String concat | || or CONCAT() |
+ |
CONCAT() |
|| |
| Limit/offset | LIMIT n OFFSET m |
OFFSET m ROWS FETCH NEXT n ROWS ONLY |
LIMIT n OFFSET m |
LIMIT n OFFSET m |
| ILIKE | ILIKE |
use LOWER() |
use LOWER() |
use LOWER() |
| Now/UTC | NOW() / NOW() AT TIME ZONE 'UTC' |
GETUTCDATE() |
UTC_TIMESTAMP() |
datetime('now') |
| IF NOT EXISTS | CREATE TABLE IF NOT EXISTS |
IF NOT EXISTS (SELECT ...) CREATE TABLE ... |
CREATE TABLE IF NOT EXISTS |
CREATE TABLE IF NOT EXISTS |
| I want to... | Command |
|---|---|
| See what's available | dbshift --help |
| Scaffold a new project | dbshift new --name MyApp --provider postgresql |
| Check my setup | dbshift info |
| Create a migration | dbshift create --name Foo --type schema |
| Create a repeatable | dbshift create --name RefreshView --type repeatable |
| Validate all scripts | dbshift validate |
| Preview changes | dbshift plan |
| Create tracking tables | dbshift init -c "..." |
| Deploy | dbshift migrate -c "..." |
| Check what's deployed | dbshift status |
| Roll back | dbshift rollback |
| Fix a failed migration | dbshift repair or dbshift repair --version 003 |
| See the audit log | dbshift history |
| Run without DB | all commands using --in-memory |
| Switch database engine | -p sqlserver or -p mysql or -p sqlite |
| Output as JSON | add --json to any command |
| Skip prompts | add --yes to any command |
| Use a different config | --config /path/to/repo/root |