데이터베이스 마이그레이션: Dev 및 Production 환경 안전하게 관리하기
Source: Dev.to
This is Day 8 of Building SaaS Solo – Design, Implementation, and Operation Advent Calendar 2025.
The approach described in this article is something I arrived at through trial and error. If you know a better way, please let me know in the comments.
일반적인 마이그레이션 관리 접근법
ORM 마이그레이션 기능 사용
ORMs like Drizzle ORM, Prisma, and TypeORM have built-in migration features.
# For Drizzle ORM
npx drizzle-kit generate # Generate migrations from schema
npx drizzle-kit migrate # Apply migrations
When you write table definitions in TypeScript, the ORM detects changes and automatically generates SQL such as ALTER TABLE.
장점
- 코드 변경으로부터 마이그레이션 SQL을 자동으로 생성
- 데이터베이스 테이블에 애플리케이션 히스토리 관리
- 한 명령으로 적용 가능
도전 과제
- 복잡한 데이터 마이그레이션(예: 기존 데이터 변환) 처리 어려움
- 실행될 내용 파악이 어려울 때가 있음
여러 환경을 관리할 때의 과제
ORM migration features are convenient for single‑environment management, but separate development and production environments introduce challenges:
- 스키마가 개발과 프로덕션 사이에 동기화되지 않을 수 있음
- “개발에 적용됐지만 아직 프로덕션에 적용되지 않음” 상태를 추적하기 어려움
- 언제 무엇이 적용됐는지 파악하기 어려움
I faced these same challenges while developing Memoreru, my indie project, and through trial and error arrived at my current operational rules.
인디 프로젝트를 위한 마이그레이션 관리
순차 파일 관리
Migration files are created by Claude Code and managed with sequential numbers.
database/migrations/
├── sql/
│ ├── 001_create_users_table.sql
│ ├── 002_create_posts_table.sql
│ ├── 003_add_user_profile.sql
│ ├── 004_add_status_column.sql
│ └── …
├── scripts/
│ └── migrate.sh
├── status.json
└── README.md
순차 관리의 장점
- 한눈에 보이는 적용 순서
- 파일 이름에 스키마가 나타내는 시점이 명시
- 프로덕션과 개발 간 차이를 쉽게 파악
SQL 파일을 직접 관리하는 이유
Instead of using Drizzle ORM’s drizzle-kit generate, I create SQL files directly. Schema definitions themselves remain managed with Drizzle ORM, preserving type safety.
SQL 파일을 직접 관리하는 이유
- 복잡한 변경(데이터 마이그레이션 포함) 처리 용이
- 실행될 내용을 완전히 이해 가능
- 문제 해결이 간단
개발 및 프로덕션을 위한 공유 마이그레이션 스크립트
공유 스크립트를 사용하는 이유
# Development environment
./database/migrations/scripts/migrate.sh dev 004_add_status_column.sql
# Production environment
./database/migrations/scripts/migrate.sh pro 004_add_status_column.sql
공유 스크립트의 장점
- 리허설 효과 – 개발에서 동일 절차를 실행해 프로덕션 문제를 사전에 발견
- 통합 절차 – 워크플로우 차이로 인한 사고 방지
- 중앙화된 로깅 – 두 환경 모두 동일 형식의 실행 로그 확보
환경별 차이점
| 항목 | 개발 | 프로덕션 |
|---|---|---|
| 연결 정보 | .env.local에서 자동 로드 | 매번 수동 입력 |
| 백업 권고 | 없음 | 경고 표시; 수동 백업 필요 |
Entering the production connection string each time is tedious, but it acts as a safety measure to prevent accidental operations on the wrong database. I also avoid giving Claude Code the production DB credentials, eliminating the risk of AI‑driven mishaps. Backups are taken with pgAdmin before applying migrations in both environments.
안전 메커니즘
The script includes:
- 확인 흐름 – 마이그레이션 적용 전 프롬프트 표시
- 연결 테스트 – 먼저 DB 연결 여부 확인
- 자동 로그 저장 –
logs/migrations/에 로그를 저장해 추후 검토 가능
The specific implementation was generated by Claude Code; you can describe your requirements and let it produce a suitable script.
status.json을 이용한 중앙화된 상태 관리
{
"lastUpdated": "2025-12-04",
"environments": {
"dev": {
"name": "Development",
"lastApplied": "004_add_status_column",
"appliedAt": "2025-12-04"
},
"pro": {
"name": "Production",
"lastApplied": "003_add_user_profile",
"appliedAt": "2025-11-30"
}
},
"pending": {
"pro": ["004_add_status_column"]
}
}
보류 중인 프로덕션 마이그레이션 확인
# Display pending list
jq '.pending.pro' database/migrations/status.json
# => ["004_add_status_column"]
You can see at a glance which migrations have been applied to dev but not yet to production.
자동 업데이트
After applying migrations, the script automatically updates status.json, eliminating manual edits and preventing forgotten updates.
실용적인 팁
팁 1: 파괴적인 변경을 점진적으로 수행하기
-- Step 1: Add new column
ALTER TABLE contents ADD COLUMN new_name TEXT;
-- Step 2: Migrate data
UPDATE contents SET new_name = old_name;
-- Step 3: Drop old column (in a separate migration)
ALTER TABLE contents DROP COLUMN old_name;
Checking application behavior between steps 2 and 3 minimizes impact if problems arise.
팁 2: 롤백 SQL 준비하기
-- Migration
ALTER TABLE contents ADD COLUMN status TEXT DEFAULT 'draft';
-- Rollback (execute only when needed)
-- ALTER TABLE contents DROP COLUMN status;
Leave rollback statements as comments for quick reference.
팁 3: Claude Code와의 협업 규칙
Document migration operation rules in CLAUDE.md:
## Migration Operations
- Don't execute SQL directly with `psql`
- Always apply via `migrate.sh` script
- Rehearse in dev environment before production
- Commit `status.json` after applying
These rules prevent AI agents from accidentally running raw SQL.
요약
잘 작동하는 점
- 연대기 추적을 위한 순차 파일 관리
- 공유 dev/production 스크립트로 리허설 수행
status.json을 통한 중앙화된 상태 관리- 실수 방지를 위한 확인 흐름
주의해야 할 점
- 변경량이 많아지면 수동 SQL 관리가 번거로워질 수 있음
- 복잡한 데이터 마이그레이션은 대표 테스트 데이터를 사용해 먼저 검증
- 롤백 절차를 미리 계획해 두어야 함
Even for indie development, establishing clear migration rules from the start helps avoid problems later.