EmDash 코어 마이그레이션은 EmDash 자체 테이블과 콘텐츠 테이블의 표준 컬럼을 업데이트합니다. 컬렉션과 필드를 생성, 제거, 이름 변경하지는 않습니다. 콘텐츠 모델 변경은 배포된 사이트의 스키마 진화시키기를 참고하세요.
런타임 마이그레이션 모드의 기본값은 auto이므로, 기존 배포는 계속해서 시작 시 보류 중인 코어 마이그레이션을 적용합니다. 배포에서 관리하는 마이그레이션을 사용하면, 새 애플리케이션 코드가 트래픽을 받기 전에 빌드가 먼저 데이터베이스를 마이그레이션하고, 이후 런타임이 그 배포 단계를 검증하거나 신뢰하도록 할 수 있습니다.
코어 마이그레이션은 앞으로만 진행됩니다. 확실히 완료된 구문 이후라면 명령을 다시 시도할 수 있도록 작성되어 있지만, 중단된 원격 명령은 결과가 모호할 수 있습니다. 안전한 대응은 마이그레이션 전체가 실행되었거나 전혀 실행되지 않았다고 가정하지 말고, 같은 데이터베이스를 emdash migrate --status로 점검하는 것입니다.
빌드, 마이그레이션, 배포, 확인
Astro 빌드나 동기화는 .emdash/migrations.json을 작성합니다. 비밀 값이 없는 이 매니페스트에는 해당 빌드에서 사용한 정확한 EmDash 버전, 순서가 정해진 마이그레이션 집합, 로케일 구성, 어댑터 마이그레이션 실행기가 기록됩니다.
매니페스트를 생성한 의존성이 있는 프로젝트에서 이 명령을 실행하세요. 먼저 빌드하고 대상을 확인합니다.
pnpm build
pnpm emdash migrate --status
보고된 대상이 의도한 데이터베이스인지 확인한 뒤, 대화형 마이그레이션을 시작합니다. 확정하기 전에 프롬프트에서 대상을 다시 한 번 검토하세요. 그런 다음 같은 빌드를 배포하고 배포된 스키마를 확인합니다.
pnpm emdash migrate
pnpm wrangler deploy
pnpm emdash migrate --check
emdash migrate --status는 데이터베이스를 변경하지 않고 적용됨, 보류 중, 알 수 없음 마이그레이션을 보고합니다. 일반 emdash migrate 명령은 대상을 표시하고, 보류 중인 마이그레이션을 적용하기 전에 확인을 요청합니다.
--check는 마이그레이션을 절대 적용하지 않으며, 알려진 마이그레이션이 보류 중이거나 데이터베이스에 빌드가 알지 못하는 마이그레이션 기록이 있으면 0이 아닌 값으로 종료합니다. check의 “작업 필요”를 뜻하는 0이 아닌 종료 상태 없이 같은 마이그레이션 집합을 점검하고 싶다면 --status를 사용하세요. CLI 레퍼런스에서는 보류 중, 알 수 없음, 확인, 중단, 운영 오류 종료 코드를 구분해 설명합니다.
비대화형 적용과 --json을 사용하는 모든 적용에는 --expected-target-fingerprint가 필요하며, 확인된 대상이 일치하지 않으면 명령이 실패합니다. 이 옵션은 위의 대화형 워크플로가 아니라 자동화된 배포 작업에서 사용하세요.
매니페스트가 다른 곳에 저장되어 있다면 --manifest path/to/migrations.json을 사용하세요. 로컬에서 조사할 때는 --from-config [--config astro.config.mjs]가 Astro 훅을 실행하거나 서버를 시작하지 않고, 신뢰할 수 있는 프로젝트 구성을 명시적으로 평가합니다. 배포 파이프라인은 빌드 매니페스트를 사용해야 합니다.
데이터베이스를 명시적으로 선택
구성된 어댑터는 비밀 값이 없는 대상 정보를 매니페스트에 제공합니다. 자격 증명은 환경 변수에 남아 있으며, 마이그레이션 명령만 읽습니다.
| 어댑터 | 매니페스트 대상 | 기본 자격 증명 변수 | 유용한 재정의 |
|---|---|---|---|
| SQLite | 데이터베이스 경로 또는 file: URL | — | --database <path> |
| libSQL | 공개 URL | TURSO_AUTH_TOKEN | migrationAuthTokenEnv 구성 |
| PostgreSQL | 연결 변수 이름 | DATABASE_URL | --database-url-env <name> |
| Cloudflare D1 | Wrangler 바인딩 이름 | CLOUDFLARE_API_TOKEN | --d1, --account-id, --wrangler-config, --wrangler-env |
| Hyperdrive | 기본 바인딩과 오리진 변수 이름 | 바인딩별 직접 오리진 변수 | migrationConnectionStringEnv 구성 |
상대 SQLite 경로는 설치된 EmDash 패키지나 셸의 현재 하위 디렉터리가 아니라 프로젝트 루트를 기준으로 해석됩니다. PostgreSQL, libSQL, Hyperdrive 대상 레이블에는 자격 증명과 URL 매개변수가 포함되지 않습니다.
마이그레이션 전에 D1 프로비저닝
D1 데이터베이스 생성과 해당 스키마 마이그레이션은 별개의 작업입니다. emdash migrate는 없는 데이터베이스를 절대 생성하지 않습니다.
-
데이터베이스를 프로비저닝하고 프로덕션 UUID를 기록합니다.
pnpm wrangler d1 create my-site-production -
해당 UUID를
wrangler.jsonc의 의도한 바인딩과 환경에 추가합니다. -
사이트를 빌드하여 D1 바인딩이
.emdash/migrations.json에 기록되도록 합니다. -
계정 ID와 D1 Edit 권한이 있는 범위 지정 API 토큰을 설정합니다. 선택된 대상을 확인한 다음 대화형 마이그레이션을 실행합니다. 계정과 데이터베이스가 의도한 프로덕션 데이터베이스와 일치할 때만 프롬프트에서 확정하세요.
export CLOUDFLARE_ACCOUNT_ID="..." export CLOUDFLARE_API_TOKEN="..." pnpm emdash migrate \ --status \ --wrangler-config wrangler.jsonc \ --wrangler-env production pnpm emdash migrate \ --wrangler-config wrangler.jsonc \ --wrangler-env production
대신 --account-id를 --d1 <database-uuid-or-name>과 함께 제공할 수도 있습니다. 이름 조회는 정확히 하나의 데이터베이스로 확인되어야 합니다. 미리 보기 ID, 자리 표시자 ID, 충돌하는 계정, 모호한 바인딩은 안전하게 실패합니다(fail closed).
CI에서 D1 마이그레이션 구성
EmDash는 마이그레이션을 적용하는 동안 D1 데이터베이스에 마이그레이션 잠금을 유지합니다. emdash migrate와 auto 모드의 런타임 마이그레이션 모두 마찬가지입니다. 잠금이 유지되는 동안 시작한 두 번째 실행은 최대 10초 동안 기다립니다. 그 시간 안에 첫 번째 실행이 끝나면 두 번째 실행은 아무것도 적용하지 않고 성공하며, 그렇지 않으면 마이그레이션을 적용하지 않고 실패합니다. 두 번째 작업이 실패하는 대신 CI 대기열에서 기다리도록, 계정과 D1 데이터베이스 UUID당 한 번에 하나의 마이그레이션 작업만 실행하세요.
CI 환경에 다음 시크릿과 변수를 설정합니다.
- 시크릿
CLOUDFLARE_API_TOKEN: D1 Edit 권한이 있는 범위 지정 토큰. - 변수
CLOUDFLARE_ACCOUNT_ID: 데이터베이스를 소유한 Cloudflare 계정 ID. - 변수
D1_DATABASE_ID: 프로덕션 D1 데이터베이스 UUID. - 변수
EMDASH_TARGET_FINGERPRINT: 로컬에서 계정과 데이터베이스를 검토한 뒤emdash migrate --status가 출력하는 핑거프린트.
다음 GitHub Actions 워크플로는 이 값을 사용하며, 변경할 수 없는 두 D1 식별자로 동시성 그룹의 키를 구성합니다. 적용 단계는 비대화형이므로 검토한 대상 핑거프린트를 명시적으로 제공합니다.
name: Deploy
on:
workflow_dispatch:
concurrency:
group: emdash-migrations-${{ vars.CLOUDFLARE_ACCOUNT_ID }}-${{ vars.D1_DATABASE_ID }}
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Inspect EmDash migration target
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
pnpm emdash migrate --status --json \
--account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
--d1 "${{ vars.D1_DATABASE_ID }}"
- name: Apply EmDash migrations
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
EMDASH_TARGET_FINGERPRINT: ${{ vars.EMDASH_TARGET_FINGERPRINT }}
run: |
pnpm emdash migrate \
--account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
--d1 "${{ vars.D1_DATABASE_ID }}" \
--expected-target-fingerprint "$EMDASH_TARGET_FINGERPRINT"
- run: pnpm wrangler deploy
- name: Check EmDash migrations
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
pnpm emdash migrate --check \
--account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
--d1 "${{ vars.D1_DATABASE_ID }}"
변경된 대상을 로컬에서 검토한 뒤에만 EMDASH_TARGET_FINGERPRINT를 업데이트하세요. 핑거프린트에는 자격 증명이 들어 있지 않지만, 계정과 데이터베이스를 확인하지 않고 변경하면 잘못된 데이터베이스를 마이그레이션하는 것을 막아 주는 보호 장치가 사라집니다.
고착된 마이그레이션 잠금 해제
D1 마이그레이션 실행이 마이그레이션 잠금을 해제하기 전에 멈추면 잠금이 유지된 채 남습니다. 이는 emdash migrate 도중 CI 작업이 취소되었을 때, auto 모드의 Worker가 런타임 마이그레이션 도중 멈췄을 때, 또는 개발 서버가 마이그레이션을 적용하는 도중 중지되었을 때 발생합니다. 마이그레이션 오류로 실패한 실행은 잠금을 해제합니다. EmDash는 자신이 유지하지 않은 잠금을 해제하지 않습니다. 보유자가 여전히 마이그레이션을 적용하는 중이거나, 마이그레이션 도중에 멈췄을 수 있기 때문입니다. 잠금이 해제되기 전까지 사이트는 보류 중인 마이그레이션을 적용할 수 없으며, auto 모드에서는 EmDash가 초기화에 실패합니다.
잠금이 1분 넘게 유지되면 emdash migrate와 런타임 마이그레이션은 더 이상 기다리지 않고 다음 오류를 보고합니다.
The migration lock has been held since 2026-09-01T12:00:00.000Z (lock 1788264000000). A migration may still be running; if none is, check the database and release the lock: https://docs.emdashcms.com/deployment/core-migrations/#release-a-stuck-migration-lock
원격 D1 데이터베이스의 잠금을 해제하려면 emdash migrate를 사용하므로, 마이그레이션 전에 D1 프로비저닝에서 설명한 대로 .emdash/migrations.json을 작성한 빌드와 D1 Edit 권한이 있는 API 토큰이 필요합니다.
-
해당 데이터베이스를 대상으로 실행 중인 마이그레이션 작업, 배포, 다른
emdash migrate명령이 없는지 확인합니다. -
마이그레이션에서 사용한 것과 같은 대상 옵션으로 잠금과 마이그레이션 집합을 점검합니다.
pnpm emdash migrate --status보고서는 잠금과 해당 id로 시작합니다. 멈춘 실행이 마이그레이션을 적용하는 중이었다면, 그것은 첫 번째 보류 중인 마이그레이션이며 일부만 적용되었을 수 있습니다.
Migration lock: held since 2026-09-01T12:00:00.000Z (id 1788264000000)auto모드의 Worker도 마이그레이션을 적용하는 동안 잠금을 유지할 수 있습니다. 1분 이상 지난 뒤 명령을 다시 실행하고, 알려진 적용된 마이그레이션이 계속 바뀌는 동안에는 잠금을 해제하지 말고 기다리세요. -
해당 id로 잠금을 해제합니다. 명령이 대상 확인을 요청하며, 잠금이 여전히 그 id일 때만 해제합니다. 비대화형 셸에서는
--status가 출력한 대상 핑거프린트와 함께--expected-target-fingerprint를 추가하세요.pnpm emdash migrate --release-lock 1788264000000 -
보류 중인 마이그레이션을 다시 적용합니다.
pnpm emdash migrate첫 번째 보류 중인 마이그레이션에서 적용이 실패하면, 그 마이그레이션이 중간까지 진행된 것으로 간주하고 문제 해결의 모호한 D1 쓰기 항목을 따르세요.
emdash migrate는 원격 D1 데이터베이스에만 접근합니다. 개발 서버의 로컬 D1 데이터베이스에 잠금이 유지되어 있다면, 서버를 중지하고 Wrangler로 잠금을 지우세요. DB는 바인딩 이름으로, 숫자는 오류에 표시된 잠금 id로 바꿉니다.
pnpm wrangler d1 execute DB --local --command "UPDATE _emdash_migrations_lock SET is_locked = 0 WHERE is_locked = 1788264000000"
Hyperdrive는 오리진에 연결
Hyperdrive의 마이그레이션 실행기는 오리진에 직접 PostgreSQL 연결을 엽니다. 마이그레이션 트래픽을 Hyperdrive를 거쳐 보내지 않고, 선택적 캐시 바인딩을 사용하지 않으며, Worker로부터 프라이빗 네트워크 도달 가능성을 물려받지도 않습니다.
배포 러너는 오리진에 도달할 수 있어야 합니다. 기본 바인딩별 변수가 적합하지 않다면 hyperdrive()에 migrationConnectionStringEnv를 설정하고, 그 변수는 마이그레이션 작업에만 제공하세요. 런타임 Hyperdrive 자격 증명과 직접 오리진 배포 자격 증명은 분리해서 관리하세요.
런타임 강제 적용을 점진적으로 도입
다음 EmDash 통합 구성은 개발 환경에서는 자동 마이그레이션을 유지하면서 런타임 강제 적용을 활성화합니다.
emdash({
database,
migrations: {
runtime: "check",
dev: "auto",
},
});
auto는 이전 버전과 호환되는 기본값입니다. 런타임 시작 시 보류 중인 마이그레이션을 확인하고 적용합니다.check는 한 방향 상태 쿼리를 한 번 수행하고, 알려진 마이그레이션이 보류 중이면 요청을 처리하기 전에 503을 반환합니다. 롤링 배포 중에는 더 새로운 호환 빌드의 기록을 허용합니다. EmDash가 설치하는 이미지 엔드포인트에 대한 요청은 이 확인을 실행하는 런타임 시작 과정을 건너뛰므로 계속 처리됩니다.manual은 런타임 마이그레이션이나 상태 쿼리를 수행하지 않습니다. 배포 파이프라인이 모든 빌드를 안정적으로 적용하고 확인하게 된 이후에만 사용하세요.
같은 아티팩트를 여러 환경으로 승격하는 경우 EMDASH_MIGRATIONS_MODE로 런타임 모드를 재정의할 수 있습니다. 설정과 개발 우회 라우트는 실제로 적용되는 모드를 따르며, check나 manual 뒤에서 몰래 마이그레이션할 수 없습니다.
보수적인 도입 방식은 배포 작업을 도입하는 동안에는 auto, 작업이 안정되면 check, 모든 배포에 외부 확인이 강제되면 manual입니다.
롤링 배포 중 호환성
코어 마이그레이션은 확장/배포/축소(expand/deploy/contract) 순서를 따릅니다. 배포 중에는 이전 애플리케이션 isolate와 새 애플리케이션 isolate가 확장된 데이터베이스를 대상으로 일시적으로 함께 실행될 수 있으며, 백필이 아직 진행 중일 수도 있습니다. 배포된 모든 버전이 해당 스키마 사용을 멈출 때까지는 스키마를 축소하지 마세요.
알 수 없는 적용된 마이그레이션 기록은 런타임 check에서 이 롤링 배포 방향에 한해서만 허용됩니다. CLI의 정확한 확인은 이를 보고하고, 적용은 변경을 거부합니다. 데이터베이스가 더 새롭거나 마이그레이션 기록이 갈라졌을 수 있기 때문입니다.
롤백 경계
이전 애플리케이션 아티팩트를 배포해도 코어 마이그레이션이 되돌려지지 않습니다. 보류 중인 마이그레이션을 적용하기 전에 복원 가능한 데이터베이스 백업을 만들고, 그에 맞는 애플리케이션 아티팩트를 기록해 두세요. 이전 애플리케이션이 마이그레이션된 스키마에서 실행되지 않는다면, 마이그레이션 이전 데이터베이스와 애플리케이션을 함께 복원하세요. 운영상의 롤백으로 _emdash_migrations의 행을 삭제하거나 마이그레이션의 내부 down() 함수를 실행하지 마세요.
혼합 PostgreSQL 소유권 복구
기존 PostgreSQL 사이트에서 둘 이상의 소유자로 EmDash 객체가 생성되었고, 이후 마이그레이션이 must be owner of table 같은 오류로 실패할 때 이 런북을 사용하세요. EmDash 기본 연결이 계속 사용할 표준 역할을 선택하세요. 소유권을 변경하기 전에 복원 가능한 데이터베이스 백업을 만들고, 애플리케이션 트래픽과 스키마 변경을 중단하세요.
활성 스키마의 모든 테이블을 점검합니다.
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
pg_get_userbyid(c.relowner) AS owner
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE n.nspname = current_schema()
AND c.relkind IN ('r', 'p')
ORDER BY c.relname;
EmDash 객체에는 _emdash_* 및 _plugin_* 시스템 테이블, ec_* 컬렉션 테이블, 그리고 content_taxonomies, media, options, revisions, taxonomies 같은 접두사 없는 테이블이 포함됩니다. 전용 EmDash 스키마에서는 모든 애플리케이션 테이블의 소유자가 표준 역할이어야 합니다.
EmDash는 미디어 사용 트리거에서 쓰는 PostgreSQL 함수도 생성합니다. 함수 소유권을 점검하고, 복구 명령에 쓸 수 있도록 각 함수의 인수 시그니처를 기록해 두세요.
SELECT
n.nspname AS schema_name,
p.proname AS function_name,
pg_get_function_identity_arguments(p.oid) AS arguments,
pg_get_userbyid(p.proowner) AS owner
FROM pg_proc AS p
JOIN pg_namespace AS n ON n.oid = p.pronamespace
WHERE n.nspname = current_schema()
ORDER BY p.proname, arguments;
소유권을 변경할 수 있는 슈퍼유저 또는 제공업체 역할로 일치하지 않는 각 객체를 이전하세요. 예시 이름을 그대로 복사하지 말고, 점검 결과에 나온 실제 스키마, 객체, 역할, 함수 시그니처를 사용하세요.
ALTER TABLE emdash.content_taxonomies OWNER TO emdash_app;
ALTER TABLE emdash.ec_posts OWNER TO emdash_app;
ALTER FUNCTION emdash.emdash_media_usage_capture_work() OWNER TO emdash_app;
테이블의 소유자를 변경하면 그에 딸린 인덱스, 제약 조건, 트리거도 함께 바뀌지만, 독립된 트리거 함수는 포함되지 않습니다. 모든 EmDash 테이블과 함수가 표준 소유자를 보고할 때까지 두 점검 쿼리를 반복하세요. 그런 다음 해당 역할로 연결하고, 트래픽을 다시 시작하기 전에 current_database(), current_schema(), 마이그레이션 상태를 확인하세요.
슈퍼유저가 아닌 사용자가 소유권을 이전하려면, 해당 객체를 소유하거나 소유권을 상속해야 하고, 새 소유자로 SET ROLE할 수 있어야 하며, 새 소유자가 스키마에 대한 CREATE 권한을 가져야 합니다. 관리형 PostgreSQL 제공업체에서는 이전 작업에 해당 업체의 관리자 역할이 필요할 수 있습니다.
문제 해결
- 마이그레이션 매니페스트를 찾을 수 없음. 먼저 프로젝트를 빌드하거나 동기화하세요. 표준이 아닌 아티팩트 위치에는
--manifest를 사용하거나, 로컬 조사를 위해--from-config를 명시적으로 선택하세요. - 아티팩트가 프로젝트의 EmDash와 일치하지 않음. 애플리케이션과 매니페스트를 함께 다시 빌드하고 배포하세요. 전역 설치본이 아니라 프로젝트의 CLI를 실행하세요.
- 대상이 없거나 모호함. 먼저 프로비저닝한 다음, 명시적인 데이터베이스 경로, 연결 변수 이름, D1 선택자, 또는 선택한 Wrangler 구성과 환경을 제공하세요. EmDash는 관련 없는 환경 변수나 바인딩으로 추측하지 않습니다.
- 대상 핑거프린트가 변경됨. 작업을 멈추고 표시된 계정, 환경, 데이터베이스 이름, UUID 또는 경로를 검토하세요. 의도한 대상임을 확인한 뒤에만 기대 핑거프린트를 업데이트하세요.
- 알 수 없는 마이그레이션 기록이 있음. 기록을 삭제하거나 적용을 다시 실행하지 마세요. 애플리케이션 아티팩트가 의도한 버전인지 확인하고, 더 새롭거나 갈라진 빌드가 데이터베이스를 마이그레이션했는지 조사하세요.
- D1 쓰기 결과가 모호함. 마이그레이션 명령을 다시 실행하지 마세요. 같은 계정과 D1 데이터베이스 UUID에 대해
emdash migrate --status를 실행해 결과를 점검하고, 마이그레이션이 도중에 멈췄다면 에스컬레이션하세요. - 날짜/시간 정규화에 수동 검토가 필요함. 레거시 날짜/시간이 사이트에 구성된 시간대에서 반복되거나 건너뛰는 일광 절약 시간 시간대에 해당합니다. 오류에는 영향을 받는 각 콘텐츠 행 또는 리비전이 나열됩니다. 해당 값을 명시적인 UTC 오프셋으로 수정한 뒤 마이그레이션을 다시 시도하세요. 저장된 모든 값을 확인할 수 있을 때까지 마이그레이션 사전 점검은 날짜/시간을 하나도 쓰지 않습니다.
- Hyperdrive가 연결할 수 없음. 배포 러너에서 PostgreSQL 오리진까지의 도달 가능성을 테스트하고 직접 오리진 변수를 확인하세요. Worker에서 Hyperdrive로의 연결이 된다고 해서 러너가 오리진에 도달할 수 있다는 증거는 아닙니다.