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 훅을 실행하거나 서버를 시작하지 않고 신뢰할 수 있는 프로젝트 구성을 명시적으로 평가합니다. 배포 파이프라인은 빌드 매니페스트를 소비해야 합니다.
데이터베이스를 명시적으로 선택
구성된 어댑터는 비밀 없는 대상 정보를 매니페스트에 제공합니다. 자격 증명은 환경 변수에 남으며 마이그레이션 명령만 읽습니다.
| Adapter | Manifest target | Default credential variable | Useful override |
|---|---|---|---|
| SQLite | Database path or file: URL | — | --database <path> |
| libSQL | Public URL | TURSO_AUTH_TOKEN | Configure migrationAuthTokenEnv |
| PostgreSQL | Connection variable name | DATABASE_URL | --database-url-env <name> |
| Cloudflare D1 | Wrangler binding name | CLOUDFLARE_API_TOKEN | --d1, --account-id, --wrangler-config, --wrangler-env |
| Hyperdrive | Primary binding and origin variable name | Binding-specific direct-origin variable | Configure 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초 대기합니다. 그 시간 안에 첫 실행이 끝나면 두 번째는 아무것도 적용하지 않고 성공합니다. 그렇지 않으면 마이그레이션을 적용하지 않고 실패합니다. 계정과 데이터베이스 UUID당 한 번에 하나의 마이그레이션 작업을 실행하여 두 번째 작업이 실패하는 대신 CI 대기열에서 기다리게 하세요.
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 식별자 둘 다로 concurrency 그룹을 키합니다. 적용 단계는 비대화형이므로 검토한 대상 지문을 명시적으로 제공합니다.
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 마이그레이션 실행은 잠금을 유지한 채로 둡니다. CI 작업이 emdash migrate 중 취소될 때, 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를 사용하므로 .emdash/migrations.json을 쓴 빌드와 D1 Edit 권한 API 토큰이 필요합니다. 마이그레이션 전에 D1 프로비저닝을 참조하세요.
-
마이그레이션 작업, 배포 또는 다른
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을 반환합니다. 롤링 배포 중에는 더 새 호환 빌드의 기록을 허용합니다.manual은 런타임 마이그레이션이나 상태 쿼리를 수행하지 않습니다. 배포 파이프라인이 모든 빌드를 안정적으로 적용·확인한 뒤에만 사용하세요.
같은 아티팩트가 여러 환경을 통해 승격될 때 EMDASH_MIGRATIONS_MODE가 런타임 모드를 재정의할 수 있습니다. 설정 및 개발 우회 경로는 유효 모드를 따르며 check나 manual 뒤에서 조용히 마이그레이션할 수 없습니다.
보수적 롤아웃은 배포 작업을 도입하는 동안 auto, 작업이 안정된 뒤 check, 모든 배포에 외부 확인이 강제되면 manual입니다.
롤링 배포 중 호환성
코어 마이그레이션은 expand/deploy/contract 순서를 따릅니다. 배포는 일시적으로 옛 애플리케이션 isolate와 새 isolate를 확장된 데이터베이스에 대해 실행할 수 있고, 백필이 아직 진행 중일 수 있습니다. 배포된 모든 버전이 스키마 사용을 멈출 때까지 스키마를 축소하지 마세요.
알 수 없는 적용 마이그레이션 기록은 이 롤링 배포 방향에 한해 런타임 check가 허용합니다. CLI의 정확한 확인은 이를 보고하고 apply는 변경을 거부합니다. 데이터베이스가 더 새거나 분기된 마이그레이션 이력이 있을 수 있기 때문입니다.
롤백 경계
이전 애플리케이션 아티팩트를 배포해도 코어 마이그레이션은 되돌리지 않습니다. 보류 마이그레이션을 적용하기 전에 복원 가능한 데이터베이스 백업을 만들고 일치하는 애플리케이션 아티팩트를 기록하세요. 이전 애플리케이션이 마이그레이션된 스키마에서 실행할 수 없으면 마이그레이션 전 데이터베이스와 애플리케이션을 함께 복원하세요. 운영 롤백으로 _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 제공자는 이전에 관리 역할을 요구할 수 있습니다.
문제 해결
- No migration manifest found. Build or sync the project first. Use
--manifestfor a non-standard artifact location or explicitly choose--from-configfor local investigation. - The artifact does not match project EmDash. Rebuild and deploy the application and manifest together. Run the project’s CLI instead of a global installation.
- The target is missing or ambiguous. Provision it first, then supply an explicit database path, connection-variable name, D1 selector, or selected Wrangler config and environment. EmDash does not guess from unrelated environment variables or bindings.
- The target fingerprint changed. Stop and review the displayed account, environment, database name, UUID, or path. Update the expected fingerprint only after confirming the intended target.
- Unknown migration records are present. Do not delete the records or rerun apply. Confirm that the application artifact is the intended version and investigate whether a newer or divergent build migrated the database.
- A D1 write outcome is ambiguous. Do not replay the migration command. Run
emdash migrate --statusagainst the same account and database UUID, inspect the result, and escalate if the migration stopped part-way through. - Datetime normalization requires manual review. A legacy datetime falls in a repeated or skipped daylight-saving hour in the site’s configured timezone. The error lists each affected content row or revision. Correct those values with an explicit UTC offset, then retry the migration. The migration preflight does not write any datetime until every stored value can be resolved.
- Hyperdrive cannot connect. Test reachability from the deployment runner to the PostgreSQL origin and verify the direct-origin variable. Worker-to-Hyperdrive connectivity does not prove the runner can reach the origin.