Database Commands
Database migration tools including SQL and Go migrations, app-scoped migrations, and migration management
Database Commands
The forge db command provides comprehensive database migration tools. It supports SQL and Go migrations, app-scoped migration isolation, multiple database connections, and safe rollback operations. Migrations are powered by bun/migrate under the hood.
Main Command
forge db (alias: database)
Database management tools.
forge db migrate
forge db status
forge db rollback
forge database migrate # Full aliasNote: The main command requires a subcommand. Use forge db --help to see all available subcommands.
Common Flags
Most forge db subcommands accept these flags for database connection and scoping:
| Flag | Alias | Default | Description |
|---|---|---|---|
--database | -d | "default" | Database name from project config |
--dsn | "" | Override database DSN/connection string | |
--type | -t | "" | Override database type (postgres, mysql, sqlite, mongodb) |
--app | -a | "" | App name for app-scoped migrations |
--verbose | -v | false | Show debug output (paths, table names, etc.) |
Subcommands
forge db init
Initialize migration tables in the database and create the migrations directory.
forge db init
forge db init --database=analytics
forge db init --app=api-gateway
forge db init --dsn="postgres://user:pass@localhost/mydb"Behavior:
- Creates the migration tracking tables (
bun_migrationsandbun_migration_locks) - Ensures the migrations directory exists
- Generates a
migrations.gobootstrap file if missing - When
--appis provided, creates app-scoped tracking tables (e.g.,bun_migrations_api_gateway)
forge db migrate
Run all pending migrations.
forge db migrate # Run all pending migrations
forge db migrate --database=analytics # Use a named database connection
forge db migrate --app=api-gateway # Run app-scoped migrations only
forge db migrate --dsn="postgres://..." # Override connection string
forge db migrate --verbose # Show debug informationBehavior:
- Discovers and runs all pending migrations in order
- Auto-initializes migration tables if they do not exist
- Detects Go migrations automatically and builds a temporary runner binary
- When
--appis provided, uses app-scoped migration directory and tracking tables - Shows a spinner with progress information
If both SQL and Go migration files are present, Forge builds a temporary Go binary that includes all migrations (SQL and Go) and runs them together.
forge db rollback
Rollback the last group of applied migrations.
forge db rollback # Rollback last migration group
forge db rollback --app=api-gateway # Rollback app-scoped migrations
forge db rollback --database=analytics # Use a named database connectionBehavior:
- Prompts for confirmation before rolling back
- Rolls back the last migration group (all migrations applied in the same batch)
- Supports both SQL and Go migrations
- When
--appis provided, rolls back only that app's migrations
forge db status
Show migration status -- which migrations have been applied and which are pending.
forge db status
forge db status --app=api-gateway
forge db status --database=analyticsBehavior:
- Lists all applied migrations with group ID and applied timestamp
- Lists all pending migrations
- Shows a helpful message when all migrations are up to date
Example Output:
Applied migrations:
Name Group Applied At
20240115100000_create_users 1 2024-01-15 10:00:05
20240115100100_create_products 1 2024-01-15 10:00:05
20240120090000_add_user_email 2 2024-01-20 09:00:03
Pending migrations:
20240125140000_create_orders
20240130110000_add_indexesforge db reset
Reset the database by rolling back all migrations and re-running them.
forge db reset # Interactive confirmation
forge db reset --force # Skip confirmation
forge db reset --app=api-gateway # Reset app-scoped migrations onlyFlags:
--force/-f-- Skip the confirmation prompt
This is a destructive operation. It rolls back all applied migrations and then re-runs them from scratch. Always back up your database before running this in production.
forge db create-sql
Create a new pair of SQL migration files (up and down).
forge db create-sql add_users_table
forge db create-sql add_orders_table --tx
forge db create-sql add_user_roles --app=auth-serviceFlags:
--tx-- Create transactional migration files (.tx.up.sql/.tx.down.sql)--app/-a-- Create migration in an app-scoped directory
Behavior:
- Generates timestamped migration files in the appropriate directory
- Without
--tx: creates{timestamp}_{name}.up.sqland{timestamp}_{name}.down.sql - With
--tx: creates{timestamp}_{name}.tx.up.sqland{timestamp}_{name}.tx.down.sql
Example:
forge db create-sql create_users_table
# Created: database/migrations/20240115100000_create_users_table.up.sql
# Created: database/migrations/20240115100000_create_users_table.down.sqlThe generated files are empty templates for you to fill in:
-- database/migrations/20240115100000_create_users_table.up.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);-- database/migrations/20240115100000_create_users_table.down.sql
DROP TABLE IF EXISTS users;forge db create-go
Create a new Go migration file.
forge db create-go add_seed_data
forge db create-go populate_defaults --app=api-gatewayFlags:
--app/-a-- Create migration in an app-scoped directory
Behavior:
- Creates a single
.gomigration file withinit()function boilerplate - Go migrations can execute arbitrary Go code during migration (useful for data transformations, seeding, etc.)
Go migrations require a migrations.go file in the migrations directory. Run forge db init to generate it if missing.
forge db lock
Manually acquire the migration lock. Prevents other processes from running migrations concurrently.
forge db lock
forge db lock --app=api-gatewayforge db unlock
Manually release the migration lock.
forge db unlock
forge db unlock --app=api-gatewayYou typically do not need to use lock/unlock manually. Forge acquires and releases the lock automatically when running migrate, rollback, or reset. Use these commands only to recover from a stuck lock (e.g., after a crash).
forge db mark-applied
Mark all pending migrations as applied without actually running them.
forge db mark-applied
forge db mark-applied --app=api-gatewayBehavior:
- Prompts for confirmation
- Marks all pending migrations as applied in the tracking table using no-op execution
- Useful when migrations were applied manually or outside of Forge
App-Scoped Migrations
App-scoped migrations allow you to isolate migrations for individual applications within a monorepo. Each app gets its own migration directory and its own tracking tables in the database.
How It Works
When you pass the --app flag, Forge:
- Resolves a separate migration directory for that app
- Uses namespaced tracking tables so migrations don't interfere with other apps
# Create a migration for the api-gateway app
forge db create-sql create_routes --app=api-gateway
# Run only api-gateway migrations
forge db migrate --app=api-gateway
# Check status for api-gateway only
forge db status --app=api-gatewayMigration Directory Resolution
Forge resolves app-scoped migration paths using a two-step process:
- App config override: If the app has its own
.forge.yamlwithdatabase.migrations_path, that path is used (relative to the app directory). - Centralized convention: Otherwise, Forge creates a subdirectory under the global migrations path:
<global_migrations_path>/<app-name>/.
# Default centralized layout
database/
migrations/
20240115_create_shared_table.up.sql # Global migrations
20240115_create_shared_table.down.sql
api-gateway/ # App-scoped
20240120_create_routes.up.sql
20240120_create_routes.down.sql
auth-service/ # App-scoped
20240120_create_sessions.up.sql
20240120_create_sessions.down.sqlApp Config Override
To use a custom migration path for an app, add a database section to the app's .forge.yaml:
# apps/api-gateway/.forge.yaml
app:
name: api-gateway
database:
migrations_path: db/migrations # Relative to this app's directory
seeds_path: db/seedsThis results in migrations stored at apps/api-gateway/db/migrations/ instead of the centralized location.
Table Namespacing
App-scoped migrations use namespaced tracking tables to prevent conflicts:
| App Name | Migration Table | Lock Table |
|---|---|---|
| (global) | bun_migrations | bun_migration_locks |
api-gateway | bun_migrations_api_gateway | bun_migration_locks_api_gateway |
auth-service | bun_migrations_auth_service | bun_migration_locks_auth_service |
App names are sanitized for use as SQL identifiers: lowercased, with hyphens, dots, and spaces replaced by underscores.
Go Migrations
Go migrations allow you to write migrations in Go for complex data transformations, seeding, or operations that are difficult to express in pure SQL.
Setup
- Initialize the migrations directory:
forge db initThis creates a migrations.go file that registers the migration package.
- Create a Go migration:
forge db create-go seed_default_roles- Edit the generated file:
package migrations
import (
"context"
"fmt"
"github.com/uptrace/bun"
)
func init() {
Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
// Up migration
fmt.Println("Seeding default roles...")
_, err := db.NewInsert().
Model(&Role{Name: "admin"}).
Exec(ctx)
return err
}, func(ctx context.Context, db *bun.DB) error {
// Down migration
_, err := db.NewDelete().
Model((*Role)(nil)).
Where("name = ?", "admin").
Exec(ctx)
return err
})
}How Go Migrations Run
When Forge detects .go files in the migrations directory (besides migrations.go), it:
- Generates a temporary migration runner binary
- The runner imports your migration package and connects to the database
- Executes the requested command (
migrate,rollback,status, etc.) - Cleans up the temporary binary
This approach means you can mix SQL and Go migrations in the same directory. They are applied in timestamp order regardless of type.
Mixing SQL and Go Migrations
You can freely mix SQL and Go migration files:
database/migrations/
migrations.go # Package registration
20240115100000_create_users.up.sql # SQL migration
20240115100000_create_users.down.sql
20240120090000_seed_default_data.go # Go migration
20240125140000_add_indexes.tx.up.sql # Transactional SQL migration
20240125140000_add_indexes.tx.down.sqlDatabase Connection
Connecting via Config
Define database connections in your project's .forge.yaml or config.yaml:
# .forge.yaml
database:
connections:
default:
dsn: "postgres://user:pass@localhost:5432/myapp?sslmode=disable"
analytics:
dsn: "postgres://user:pass@localhost:5432/analytics?sslmode=disable"Use the --database flag to select a named connection:
forge db migrate --database=analyticsConnecting via DSN
Override the connection string directly:
forge db migrate --dsn="postgres://user:pass@localhost:5432/mydb?sslmode=disable"Environment Variables
Database connections support environment variable expansion. Forge loads .env files in this priority order:
.env-- Base environment.env.local-- Local overrides (gitignored).env.{environment}-- Environment-specific (e.g.,.env.production).env.{environment}.local-- Environment-specific local overrides
The environment is determined by FORGE_ENV or GO_ENV (defaults to development).
# .env
DATABASE_URL=postgres://user:pass@localhost:5432/myapp?sslmode=disable# .forge.yaml (references the env variable)
database:
connections:
default:
dsn: "${DATABASE_URL}"Migration File Conventions
SQL Migrations
SQL migration files use this naming pattern:
| Pattern | Description |
|---|---|
{timestamp}_{name}.up.sql | Standard up migration |
{timestamp}_{name}.down.sql | Standard down migration |
{timestamp}_{name}.tx.up.sql | Transactional up migration |
{timestamp}_{name}.tx.down.sql | Transactional down migration |
Transactional migrations (created with --tx) wrap the entire migration in a database transaction, ensuring atomicity.
Go Migrations
Go migration files use the pattern {timestamp}_{name}.go and must be in the same package as migrations.go.
Command Reference
| Command | Description | Database Flags | App-Scoped | Go Support |
|---|---|---|---|---|
forge db init | Initialize migration tables | Yes | Yes | -- |
forge db migrate | Run pending migrations | Yes | Yes | Yes |
forge db rollback | Rollback last migration group | Yes | Yes | Yes |
forge db status | Show migration status | Yes | Yes | Yes |
forge db reset | Reset database (rollback all + rerun) | Yes | Yes | -- |
forge db create-sql | Create SQL migration files | -- | Yes | -- |
forge db create-go | Create Go migration file | -- | Yes | -- |
forge db lock | Acquire migration lock | Yes | Yes | -- |
forge db unlock | Release migration lock | Yes | Yes | -- |
forge db mark-applied | Mark pending as applied | Yes | Yes | -- |
"Database Flags" means the command accepts --database, --dsn, and --type.
Best Practices
- Version control migrations -- Always commit migration files. They are the source of truth for your database schema.
- Write reversible migrations -- Always include a down migration for safe rollback.
- Keep migrations small and focused -- One logical change per migration is easier to review and rollback.
- Use transactional migrations -- Use
--txfor DDL changes that should be atomic. - Use Go migrations for data transformations -- Complex data seeding or transformation logic is better expressed in Go than SQL.
- Use app-scoped migrations in monorepos -- Isolate each app's schema changes to avoid conflicts and enable independent deployments.
- Back up before production migrations -- Always have a backup before running migrations in production.
- Test in development first -- Run
forge db migratelocally, verify withforge db status, then promote to staging and production.
Troubleshooting
No Migrations Found
forge db migrate
# No migrations foundSolution: Create your first migration:
forge db create-sql initial_schema
# or for app-scoped:
forge db create-sql initial_schema --app=my-appDatabase Connection Error
forge db migrate
# Error: database connection failedSolution:
# Check your database is running
pg_isready -h localhost -p 5432
# Verify your DSN
forge db migrate --dsn="postgres://user:pass@localhost:5432/mydb" --verbose
# Check your .forge.yaml or config.yaml
cat .forge.yamlMigration Lock Stuck
If a previous migration run crashed and left the lock acquired:
forge db unlock
# or for app-scoped:
forge db unlock --app=api-gatewayMissing migrations.go
If you see a warning about a missing migrations.go file:
forge db init
# or for app-scoped:
forge db init --app=api-gatewayThis generates the required package registration file for Go migrations.
How is this guide?