07-db 데이터베이스 안전 규칙
우선순위
우선순위
이 규칙은 03-security 다음으로 높은 우선순위. DB 데이터 손실은 되돌릴 수 없다.
1. DB 이름 충돌 방지 (CRITICAL)
로컬 개발 환경에서 여러 프로젝트가 동일한 PostgreSQL 인스턴스를 사용할 경우,
DB 이름이 겹치면 한 프로젝트의 migrate reset이 다른 프로젝트 데이터를 완전히 삭제한다.
또한, DB 이름이 달라도 포트 충돌로 엉뚱한 PostgreSQL 인스턴스에 연결되면 같은 효과가 발생한다 (2026-04-14 meetflow 사례 참조). 이를 방어하려면 이름 규칙 + 포트 정책 + 런타임 DB 검증(아래 1-2 섹션) 3단계가 모두 필요하다.
실제 발생 사례 (2026-04-01)
AX-Studio (구 레포)와 AX-Studio-plan (현 레포)이 동일하게 localhost:5432/ax_studio 사용.
구 레포에서 migration 작업 중 DB가 초기화되어 현 레포의 모든 사용자 데이터 소실.
실제 발생 사례 (2026-04-14, meetflow)
meetflow 와 ax-studio-plan 이 둘 다 호스트 포트 5432 사용. ax-studio-db 컨테이너가 5432를 선점한 상태에서 meetflow-postgres 컨테이너는 Created 상태로 갇힘. meetflow 앱은 localhost:5432/meetflow_dev 로 접속했지만 실제로는 ax-studio-db 인스턴스 안에 meetflow_dev DB를 생성해 회원가입 데이터를 적재. 포트를 5436으로 변경하고 docker compose down/up 후 원래 볼륨에 붙었을 때 테이블이 비어 있음을 발견.
배경: docs/guide/LOCAL_PORT_POLICY.md
DB 이름 규칙
- 프로젝트마다 고유한 DB 이름 필수
- 형식:
{프로젝트명_용도}(예:ax_studio_plan,aitem_v2,printstudio_local) - 금지:
postgres,test,dev,app,database,mydb - 두 레포가 같은 서비스라도 별도 레포 = 별도 DB
신규 프로젝트 세팅 시 충돌 확인 (에이전트 필수 실행)
# 1. 현재 로컬 DB 목록
psql -U postgres -c "\l" | grep -v template
# 2. 다른 프로젝트가 사용 중인 DB 이름 확인
find ~/dev -name ".env" -not -path "*/node_modules/*" 2>/dev/null \
| xargs grep "DATABASE_URL" 2>/dev/null
충돌 발견 시 즉시 사용자에게 보고 → 대안 이름 제안 → 사용자 승인 후 진행.
Prisma 안전 환경변수 (신규 프로젝트 세팅 시 필수)
Prisma v6.15.0+는 PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION 환경변수 미설정 시
에이전트 환경에서 파괴적 명령(migrate reset, db push --force-reset)을 자동 차단한다.
신규 프로젝트 .env.example에 반드시 포함:
# Prisma AI 안전 가드 — 값을 설정하지 않으면 에이전트의 파괴적 명령이 자동 차단됨
# 로컬 초기 세팅 시에만 "I understand the risks" 로 설정 (사람이 직접)
PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION=
1-2. DB 연결 검증 (런타임 방어선, CRITICAL)
앱 부팅 시 현재 연결된 DB 이름과 포트가 의도한 값인지 검증한다.
이름 규칙·포트 정책이 아무리 엄격해도 사람/에이전트가 레지스트리 업데이트를 빠뜨리거나 .env가 오래된 값을 가리키면 엉뚱한 DB에 붙을 수 있다. 이 섹션은 마지막 방어선이다.
왜 런타임 검증이 필요한가
세팅 시점 방어는 "빠뜨릴 수 있는" 체크리스트다. 런타임 검증은 앱이 시작할 때마다 자동 실행되어 빠뜨릴 수 없다. 이번 meetflow 사례에서 이 검증이 있었다면 첫 회원가입 요청 전에 앱이 기동 실패로 터져 데이터 소실이 0건이었을 것.
.env에 기대값 명시
# .env
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/meetflow_dev"
EXPECTED_DB_NAME=meetflow_dev # current_database() 결과와 일치해야 함
EXPECTED_DB_PORT=5433 # inet_server_port() 결과와 일치해야 함 (선택)
# 긴급 복구 전용 — 운영/스테이징에는 절대 사용 금지
# SKIP_DB_VERIFY=1
.env.example에도 두 키를 반드시 포함 (값 없이 주석으로 설명).
Prisma (Node/TypeScript)
lib/db-verify.ts 신규 생성, 앱 진입점에서 부팅 시 1회 실행:
// lib/db-verify.ts
import { PrismaClient } from '@prisma/client'
const isProd = process.env.NODE_ENV === 'production'
/**
* 실제 연결된 DB가 .env의 EXPECTED_DB_NAME과 일치하는지 부팅 시 검증한다.
* 프로덕션에서는 host/port 같은 인프라 식별자를 로그/에러 메시지에 포함하지 않는다 (03-security).
*/
export async function verifyDbConnection(prisma: PrismaClient) {
if (process.env.SKIP_DB_VERIFY === '1') {
console.warn('⚠️ SKIP_DB_VERIFY=1 — DB 검증 우회 (긴급 복구 전용)')
return
}
const expected = process.env.EXPECTED_DB_NAME
const expectedPort = process.env.EXPECTED_DB_PORT
if (!expected) {
throw new Error('EXPECTED_DB_NAME env 누락 — 07-db 규칙 위반')
}
// inet_server_port()는 integer, inet_server_addr()는 inet(소켓 연결 시 null).
// 드라이버/풀러에 따라 number | bigint 로 올 수 있어 Number()로 정규화.
const rows = await prisma.$queryRaw<
Array<{ db: string; port: number | bigint; host: string | null }>
>`SELECT current_database() AS db, inet_server_port() AS port, inet_server_addr()::text AS host`
const row = rows[0]
const port = Number(row.port)
if (row.db !== expected) {
// 프로덕션: 인프라 식별자 숨김. 개발: 진단 목적으로 전체 노출.
throw new Error(
isProd
? `❌ DB name mismatch (expected=${expected}, got=${row.db})`
: `❌ 엉뚱한 DB에 연결됨: ${row.db}@${row.host ?? 'socket'}:${port} (예상: ${expected})`
)
}
if (expectedPort && String(port) !== expectedPort) {
throw new Error(
isProd
? `❌ DB port mismatch (expected=${expectedPort})`
: `❌ DB 포트 불일치: ${port} (예상: ${expectedPort}) — docker-compose ports와 .env 싱크 확인`
)
}
if (!isProd) {
console.log(`✅ DB 연결 검증 OK: ${row.db}@${row.host ?? 'socket'}:${port}`)
}
}
// 앱 진입점 (예시)
// - Express/Hono/Fastify: app.listen() 직전에 await
// - Next.js App Router: instrumentation.ts 의 register() 안에서 await
// - Nuxt: server/plugins/db-verify.ts 에서 await
// await verifyDbConnection(prisma)
SQLAlchemy (Python)
SQLAlchemy 1.4+ / 2.0 호환 (.mappings().one() API).
# app/core/db_verify.py
import os
from sqlalchemy import text
from sqlalchemy.engine import Engine
_IS_PROD = os.environ.get("ENV", "").lower() == "production"
def verify_db_connection(engine: Engine) -> None:
"""
.env의 EXPECTED_DB_NAME과 실제 연결 DB를 비교. 프로덕션에서는 host/port를 로그/에러에 노출하지 않는다 (03-security).
"""
if os.environ.get("SKIP_DB_VERIFY") == "1":
print("⚠️ SKIP_DB_VERIFY=1 — DB 검증 우회 (긴급 복구 전용)")
return
expected = os.environ.get("EXPECTED_DB_NAME")
expected_port = os.environ.get("EXPECTED_DB_PORT")
if not expected:
raise RuntimeError("EXPECTED_DB_NAME env 누락 — 07-db 규칙 위반")
with engine.connect() as conn:
row = conn.execute(text(
"SELECT current_database() AS db, inet_server_port() AS port, "
"inet_server_addr()::text AS host"
)).mappings().one()
host = row["host"] or "socket"
port = int(row["port"])
if row["db"] != expected:
raise RuntimeError(
f"❌ DB name mismatch (expected={expected}, got={row['db']})"
if _IS_PROD
else f"❌ 엉뚱한 DB에 연결됨: {row['db']}@{host}:{port} (예상: {expected})"
)
if expected_port and str(port) != expected_port:
raise RuntimeError(
f"❌ DB port mismatch (expected={expected_port})"
if _IS_PROD
else f"❌ DB 포트 불일치: {port} (예상: {expected_port}) — compose/.env 싱크 확인"
)
if not _IS_PROD:
print(f"✅ DB 연결 검증 OK: {row['db']}@{host}:{port}")
# FastAPI 예시:
# @app.on_event("startup")
# async def _verify_db(): verify_db_connection(engine)
신규 프로젝트에 기본 포함
- 신규 프로젝트 세팅 시 에이전트는 반드시 위 스니펫을 추가하고 진입점에
verify_db_connection()호출을 삽입한다 (08-local-env 체크리스트 9번). - 기존 프로젝트에 스니펫이 없으면 관련 작업 중 발견 시 사용자에게 추가 제안.
검증 실패 시
앱이 기동 실패로 터진다. 조용히 동작하지 않는다. 에이전트는 로그를 읽고 진단:
.env의DATABASE_URL포트 ↔docker-compose.ymlports ↔ PORT_REGISTRY 값 3자 일치 확인docker ps로 실제 어느 컨테이너가 해당 호스트 포트를 잡고 있는지 확인EXPECTED_DB_NAME이 실제 만든 DB 이름과 일치하는지 확인
한계 (이 방어선이 못 막는 시나리오)
이 검증은 앱 프로세스가 부팅될 때만 실행된다. 아래 경로로는 검증을 거치지 않으므로 별도 주의 필요:
- Prisma migration / seed 스크립트 —
prisma migrate deploy,prisma db seed등은 별도 프로세스로 실행되며verifyDbConnection()을 거치지 않는다. 잘못된 포트로 migration이 엉뚱한 DB를 초기화할 수 있다. 대응: migration 실행 전 사람이psql "$DATABASE_URL" -c "SELECT current_database()"로 수동 확인 권장. - SSR/Edge 프리렌더링 — Next.js
next build의 static generation이 DB에 접근하면 검증 훅 시점이 불분명. 필요 시 각 data loader 상단에서 추가 검증 호출. - 테스트/CI 환경 — 테스트용 DB 이름이 다르면
.env.test또는CI환경변수 기반으로EXPECTED_DB_NAME을 분기하거나, CI에서는SKIP_DB_VERIFY=1허용 (단 로컬/운영에선 절대 설정 금지). - 긴급 복구 — DB 장애 중 임시로 다른 DB에 붙여야 하면
SKIP_DB_VERIFY=1로 우회. 복구 후 즉시 해제.
2. 파괴적 Migration 명령어 금지 (CRITICAL)
governance hook (guard-branch.sh) + tooling hook이 migrate reset, db push --force-reset, DROP DATABASE/TABLE 패턴을 자동 차단한다.
아래 테이블은 에이전트 판단 기준으로 유지:
에이전트 절대 실행 금지
| 명령어 | 위험도 | 이유 |
|---|---|---|
prisma migrate reset |
🔴 치명 | DB 전체 삭제 후 재생성 — 모든 데이터 소실 |
prisma migrate dev |
🟠 높음 | shadow DB로 drift 자동 해결 시 데이터 손실 가능 |
prisma db push --force-reset |
🔴 치명 | 스키마 강제 초기화 |
alembic downgrade |
🟠 높음 | 마이그레이션 롤백 — 컬럼/테이블 삭제 가능 |
alembic downgrade base |
🔴 치명 | 전체 마이그레이션 롤백 — 스키마 완전 초기화 |
DROP DATABASE |
🔴 치명 | DB 전체 삭제 |
DROP TABLE |
🔴 치명 | 테이블 삭제 |
TRUNCATE |
🟠 높음 | 테이블 전체 데이터 삭제 |
DELETE FROM (WHERE 없음) |
🔴 치명 | 전체 행 삭제 |
에이전트 허용 명령어
| 명령어 | 설명 |
|---|---|
prisma migrate status |
상태 확인만 (읽기 전용) |
prisma migrate deploy |
미적용 migration만 순서대로 적용 (데이터 보존) |
prisma generate |
클라이언트 코드 재생성만 |
alembic upgrade head |
미적용 migration 순서대로 적용 |
alembic current |
현재 revision 확인만 |
prisma migrate dev 사용 기준 (예외 허용)
prisma migrate dev는 shadow DB로 drift를 자동 해결하므로 원칙적으로 금지.
단, 아래 조건을 모두 만족하면 사람이 직접 실행 가능 (에이전트 실행 금지):
신규 프로젝트 (DB에 보존할 데이터 없음)
✅ 허용 조건:
- 아직 운영/스테이징 배포 전인 로컬 전용 DB
- migrate status에서 drift 또는 미생성 migration 감지된 상태
에이전트 안내 형식 (분류: [차단:판단근거] — 05-responses 참조):
"[차단:판단근거] 이 명령 자체는 위험하지 않습니다. 다만 '이 프로젝트가 정말
신규인지' (보존할 데이터가 없는지, 스테이징/운영 배포 이력이 없는지)는 .env와
실제 DB 상태를 아는 사람만 확신할 수 있어 직접 실행을 요청합니다.
아래 명령어를 실행하세요:
cd {path} && npx prisma migrate dev --name {desc}
실행 후 생성된 migration 파일을 커밋해주세요."
중요: 이 케이스를 [차단:위험](R2)로 안내하지 마라. 실제 위험도는 R0~R1이고,
사람이 실행하는 이유는 "신규 여부 판단 근거가 사람에게만 있기 때문"이다.
R2/R1 가역성과 판단 근거 부재는 서로 다른 차단 사유다.
기존 프로젝트 (운영/스테이징 배포 이력 있음) — 금지
❌ 이유: shadow DB 생성 중 drift 자동 해결 시 데이터 손실 가능
기존 프로젝트 스키마 변경 절차:
1. prisma migrate status → 현재 상태 확인 후 사용자 보고
2. schema.prisma 수정
3. 에이전트가 migration SQL 초안 작성 → 사람이 검토
4. prisma migrate deploy → 적용 (데이터 보존)
사용자가 금지 명령어 실행을 요청할 때
"prisma migrate reset 실행해줘" 요청 시:
❌ 바로 실행 금지
✅ 아래 경고 후 확인 문구 재입력 대기 (03-security 가역성 R2 등급):
"⚠️ migrate reset은 DB의 모든 데이터를 삭제합니다. (R2 — 비가역)
현재 DB: {DB_NAME} @ {HOST}
실행 전 반드시 백업을 권장합니다:
pg_dump -U postgres {DB_NAME} > backup_$(date +%Y%m%d_%H%M).sql
계속하려면 아래 문구를 정확히 입력하세요:
> CONFIRM reset-{DB_NAME}-{오늘날짜}
예: CONFIRM reset-ax-studio-plan-20260401"
3. Migration 실행 프로세스 (에이전트 표준)
스키마 변경 후 반드시 이 순서
1. prisma migrate status → 현재 상태 확인 및 사용자 보고
2. schema.prisma 수정
3. migration 파일 생성 안내 (실행은 사람이)
4. 사용자 승인 후: prisma migrate deploy
5. prisma generate
migration 실행 전 사용자 보고 형식
### Migration 실행 계획
- 대상 DB: ax_studio_plan @ localhost:5432
- 변경 내용: Page 모델에 layoutSchema Json? 컬럼 추가
- Migration 파일: 20260401_add_layout_schema_to_page
- 데이터 영향: 없음 (Optional 컬럼 추가)
실행 명령어:
cd backend/api && npx prisma migrate deploy
진행할까요?
데이터 손실 가능성 있는 변경(컬럼 삭제, 타입 변경, NOT NULL 추가):
⚠️ 데이터 손실 가능
- 삭제되는 컬럼: old_column (기존 데이터 영구 삭제)
- 백업 후 진행 권장
4. Migration 파일 보호
- migration 파일은 절대 수정/삭제 금지 → 새 migration으로 변경사항 추가
- 프로덕션 배포 후 migration을 로컬에서
migrate dev로 재생성 금지 - 스키마 변경 = 반드시 새 migration 파일 —
schema.prisma만 바꾸고 끝내지 않음 - migration 파일명:
{timestamp}_{설명}(예:20260401011547_add_layout_schema_to_page)
5. 환경별 Migration 명령어
### 로컬 개발
cd backend/api
npx prisma migrate deploy # Prisma
alembic upgrade head # Python/Alembic
### 스테이징/운영 (Docker)
docker compose exec api npx prisma migrate deploy
docker compose exec backend alembic upgrade head
### Railway / 원격
railway run npx prisma migrate deploy
6. 로컬 백업 운영 규칙
파괴적 작업 전 백업 (에이전트가 안내, 사람이 실행)
# PostgreSQL 백업
pg_dump -U postgres {DB_NAME} > backup_$(date +%Y%m%d_%H%M).sql
# 복구
psql -U postgres {DB_NAME} < backup_20260401_1200.sql
자동 백업 스크립트
프로젝트에 scripts/backup-db.sh 추가 권장 (TOOLING_SETUP.md 참조).
백업 파일은 .gitignore에 포함 (*.sql, backups/).
7. DB 변경 완료 보고 형식 (필수)
### DB 변경 내용
- 변경 모델/컬럼: Page.layoutSchema (Json? 추가)
- Migration 파일: 20260401011547_add_layout_schema_to_page
### 환경별 실행 명령어
# 로컬: cd backend/api && npx prisma migrate deploy
# 운영: docker compose exec api npx prisma migrate deploy
### 데이터 영향
- [영향 없음] Optional 컬럼 추가, 기존 행은 null로 유지
데이터 손실 가능성 있으면:
### ⚠️ 데이터 손실 가능
- 사유: old_column 컬럼 삭제
- 백업 후 진행 권장
- 사람 승인 대기 중