db-migrate
vndee/engineering-skills/.claude/skills/db-migrate/SKILL.md
Use when creating or modifying database schema in Go projects using golang-migrate with PostgreSQL
Skill3 starsChanged 7 months ago
- Deletes or force-pushes
What's in it
- Database Migrations (golang-migrate)
- Overview
- When to Use
- File Naming
- Makefile Targets
- Safe DDL Patterns
- Zero-Downtime Checklist
- PostgreSQL Type Conventions
- Enum Patterns
- Verification
- Common Mistakes
- Chains
---
name: db-migrate
description: Use when creating or modifying database schema in Go projects using golang-migrate with PostgreSQL
---
# Database Migrations (golang-migrate)
## Overview
Safe, reversible PostgreSQL schema changes using golang-migrate. Every migration must be idempotent and have a working rollback.
**Core principle:** Every `up.sql` must have a working `down.sql`. Verify with up → down → up cycle.
## When to Use
- Adding tables, columns, indexes, or constraints
- Modifying existing schema
- Creating enum types
- Adding seed data via migrations
## File Naming
```
migrations/
000001_create_users_table.up.sql
000001_create_users_table.down.sql
000002_add_user_email_index.up.sql
000002_add_user_email_index.down.sql
```
Sequential numbering. Descriptive names. Always pairs.
## Makefile Targets
```makefile
MIGRATE=migrate -path migrations -database "$(DATABASE_URL)"
migrate-up:
$(MIGRATE) up
migrate-down:
$(MIGRATE) down 1
migrate-create:
migrate create -ext sql -dir migrations -seq $(name)
migrate-force:
$(MIGRATE) force $(version)
```
## Safe DDL Patterns
```sql
-- Tables: always IF NOT EXISTS
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Indexes: CONCURRENTLY to avoid locks
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);
-- Columns: nullable first for zero-downtime
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone TEXT;
-- Later migration: backfill, then NOT NULL
```
## Zero-Downtime Checklist
| Operation | Safe Approach |
|-----------|--------------|
| Add column | Add as nullable → backfill → add NOT NULL constraint |
| Drop column | Stop reading → deploy → drop in next migration |
| Rename column | Add new → copy data → update code → drop old |
| Add NOT NULL | Add with DEFAULT first |
| Add index | Use `CONCURRENTLY` |
| Drop table | Remove all references first |
## PostgreSQL Type Conventions
| Go Type | Postgres Type |
|---------|--------------|
| `uuid.UUID` | `UUID` |
| `time.Time` | `TIMESTAMPTZ` |
| `string` | `TEXT` |
| `int64` | `BIGINT` |
| `float64` | `DOUBLE PRECISION` |
| `map/struct` | `JSONB` |
| `bool` | `BOOLEAN` |
## Enum Patterns
```sql
-- up.sql
CREATE TYPE user_role AS ENUM ('admin', 'member', 'viewer');
ALTER TABLE users ADD COLUMN role user_role NOT NULL DEFAULT 'member';
-- down.sql
ALTER TABLE users DROP COLUMN IF EXISTS role;
DROP TYPE IF EXISTS user_role;
-- Adding values to existing enum (cannot be in transaction)
ALTER TYPE user_role ADD VALUE IF NOT EXISTS 'moderator';
```
## Verification
Always run the idempotency check:
```bash
make migrate-up && make migrate-down && make migrate-up
```
## Common Mistakes
- Missing `down.sql` — always write rollback
- Using `VARCHAR(n)` — prefer `TEXT` in Postgres
- `TIMESTAMP` without timezone — always use `TIMESTAMPTZ`
- Non-concurrent index creation on large tables — causes locks
- Adding NOT NULL without DEFAULT — fails on existing rows
## Chains
- **REQUIRED:** Update CLAUDE.md if new migration commands are added (`claude-md`)
More agent context in vndee/engineering-skills
36 other files this repository gives its agents.
Skill
- adr.claude/skills/adr/SKILL.md
- analytics.claude/skills/analytics/SKILL.md
- api-contract.claude/skills/api-contract/SKILL.md
- api-design.claude/skills/api-design/SKILL.md
- ci-pipeline.claude/skills/ci-pipeline/SKILL.md
- claude-md.claude/skills/claude-md/SKILL.md
- code-quality.claude/skills/code-quality/SKILL.md
- data-model.claude/skills/data-model/SKILL.md
- debug.claude/skills/debug/SKILL.md
- deploy.claude/skills/deploy/SKILL.md
- dep-update.claude/skills/dep-update/SKILL.md
- disk-cleanup.claude/skills/disk-cleanup/SKILL.md
- docker-build.claude/skills/docker-build/SKILL.md
- eng-lead.claude/skills/eng-lead/SKILL.md
- event-driven.claude/skills/event-driven/SKILL.md
- fullstack-healthcheck.claude/skills/fullstack-healthcheck/SKILL.md
- go-feature.claude/skills/go-feature/SKILL.md
- go-integration-test.claude/skills/go-integration-test/SKILL.md
- go-refactor.claude/skills/go-refactor/SKILL.md
- go-scaffold.claude/skills/go-scaffold/SKILL.md
- incident-response.claude/skills/incident-response/SKILL.md
- interactive-clarify.claude/skills/interactive-clarify/SKILL.md
- observability.claude/skills/observability/SKILL.md
- onboarding.claude/skills/onboarding/SKILL.md
- product-spec.claude/skills/product-spec/SKILL.md
- py-feature.claude/skills/py-feature/SKILL.md
- py-integration-test.claude/skills/py-integration-test/SKILL.md
- py-migrate.claude/skills/py-migrate/SKILL.md
- py-refactor.claude/skills/py-refactor/SKILL.md
- py-scaffold.claude/skills/py-scaffold/SKILL.md
- react-feature.claude/skills/react-feature/SKILL.md
- react-refactor.claude/skills/react-refactor/SKILL.md
- react-scaffold.claude/skills/react-scaffold/SKILL.md
- review-code.claude/skills/review-code/SKILL.md
- security.claude/skills/security/SKILL.md
- system-design.claude/skills/system-design/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

