Skip to content
Back to skills

Create Migration

ASecurity

Create, apply, and rollback database migrations using goose

  • 666 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added February 8, 2026
databasesgobashsqldockergitdatabaseci/cd

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill create-migration --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Create Migration?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Create Migration
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-create-migration/badge)](https://www.skillsdirectory.com/skills/majiayu000-create-migration)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: create-migration
description: Create, apply, and rollback database migrations using goose
---

# Create Migration Skill

Use this skill when making database schema changes.

---

## Scope

- Adding new tables
- Modifying columns or constraints
- Creating or dropping indexes
- Any DDL operation

## Creating a New Migration

### Step 1: Generate Migration File

```bash
task migrate:create -- your_migration_name
```

**Naming conventions:**
- Use snake_case
- Be descriptive but concise
- Use prefixes: `add_`, `create_`, `drop_`, `modify_`, `update_`

**Examples:**
```bash
task migrate:create -- add_weapon_stats_table
task migrate:create -- add_index_on_trader_offers
task migrate:create -- modify_item_properties_column
```

This creates a timestamped file in `migrations/`:
```
migrations/YYYYMMDDHHMMSS_your_migration_name.go
```

### Step 2: Write the Migration

The generated file will have this structure:

```go
package migrations

import (
    "database/sql"
    "github.com/pressly/goose/v3"
)

func init() {
    goose.AddMigrationContext(upYourMigrationName, downYourMigrationName)
}

func upYourMigrationName(ctx context.Context, tx *sql.Tx) error {
    // This code is executed when the migration is applied.
    return nil
}

func downYourMigrationName(ctx context.Context, tx *sql.Tx) error {
    // This code is executed when the migration is rolled back.
    return nil
}
```

**In the `up` function:**
- Write SQL to apply the change
- Use `tx.ExecContext(ctx, "SQL HERE")`

**In the `down` function:**
- Write SQL to reverse the change
- Make it possible to rollback safely

**Example:**

```go
func upAddWeaponStatsTable(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `
        CREATE TABLE weapon_stats (
            id SERIAL PRIMARY KEY,
            weapon_id VARCHAR(255) NOT NULL,
            recoil_vertical INT NOT NULL,
            recoil_horizontal INT NOT NULL,
            ergonomics INT NOT NULL,
            created_at TIMESTAMP DEFAULT NOW()
        );
        CREATE INDEX idx_weapon_stats_weapon_id ON weapon_stats(weapon_id);
    `)
    return err
}

func downAddWeaponStatsTable(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `DROP TABLE IF EXISTS weapon_stats;`)
    return err
}
```

### Step 3: Apply the Migration

```bash
# Recommended for DevContainer (assuming database is running)
task migrate:up

# Use if database needs to be started via Docker Compose
task migrate:up:docker
```

**What it does:**
1. `migrate:up`: Builds the migration binary and runs migrations against the existing database.
2. `migrate:up:docker`: Ensures PostgreSQL is running via Docker Compose, then applies migrations.

**Verify migration applied:**
```bash
docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "\dt"
```

### Step 4: Test the Migration

**Test the migration works:**
```bash
# Apply migration
task migrate:up

# Run integration tests
task test:integration
```

**Test rollback works:**
```bash
# Rollback the migration
task migrate:down

# Verify database is in previous state
docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "\dt"

# Reapply
task migrate:up
```

## Migration Best Practices

### DO:
- ✅ Keep migrations small and focused (one logical change per migration)
- ✅ Provide a `down` function that reverses the change when feasible
- ✅ Test both `up` and `down` migrations before merging
- ✅ goose wraps migrations in transactions automatically
- ✅ Add indexes for foreign keys and frequently queried columns
- ✅ Use `IF NOT EXISTS` / `IF EXISTS` for safety when appropriate

### DON'T:
- ❌ Modify existing migration files after they're merged (create a new migration instead)
- ❌ Use application code in migrations (keep them SQL-only)
- ❌ Make data changes that can't be reversed in `down`
- ❌ Forget to handle the error return value
- ❌ Create huge migrations that change many things at once

## Common Migration Patterns

### Add a Table

```go
func upCreateTableName(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `
        CREATE TABLE table_name (
            id SERIAL PRIMARY KEY,
            field1 VARCHAR(255) NOT NULL,
            field2 INTEGER DEFAULT 0,
            created_at TIMESTAMP DEFAULT NOW()
        );
    `)
    return err
}

func downCreateTableName(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `DROP TABLE IF EXISTS table_name;`)
    return err
}
```

### Add a Column

```go
func upAddColumnToTable(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `
        ALTER TABLE table_name 
        ADD COLUMN new_column VARCHAR(255);
    `)
    return err
}

func downAddColumnToTable(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `
        ALTER TABLE table_name 
        DROP COLUMN IF EXISTS new_column;
    `)
    return err
}
```

### Add an Index

```go
func upAddIndexOnTable(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `
        CREATE INDEX idx_table_column ON table_name(column_name);
    `)
    return err
}

func downAddIndexOnTable(ctx context.Context, tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, `
        DROP INDEX IF EXISTS idx_table_column;
    `)
    return err
}
```

## Troubleshooting

**Migration fails with "docker": executable file not found:**
- This happens if you try to run `task migrate:up:docker` in an environment without Docker (like a devcontainer).
- Use `task migrate:up` instead if your database is already running.

**Migration fails to apply:**
- Check SQL syntax
- Verify table/column names exist
- Check if migration was already partially applied
- View database logs: `docker compose logs postgres`

**Can't rollback migration:**
- Check if `down` function properly reverses the `up` function
- Some operations (like dropping columns with data) might need manual intervention
- Consider if rollback is safe with existing data

**Migration applied but tests fail:**
- Verify the schema change matches your model expectations
- Check if indexes are created correctly
- Ensure foreign key constraints are correct

**"goose: no migrations to run" but migration file exists:**
- Ensure the file is in `migrations/` directory
- Check the filename format: `YYYYMMDDHHMMSS_name.go`
- Verify the file has `package migrations` at the top
- Rebuild: `task migrate:build`

## Viewing Migration Status

```bash
# See applied migrations in database
docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "SELECT * FROM goose_db_version;"

# See migration files
ls -la migrations/
```

## CI/CD

In CI, migrations run via:
```bash
task migrate:ci
```

This skips the `compose:postgres:up` dependency (database already running in CI).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.