migrating-dagster-to-airflow

작성자: astronomer

Dagster 프로젝트를 Astro의 Apache Airflow 3로 마이그레이션하기 위한 가이드입니다. 사용자가 Dagster(또는 Dagster+) 코드를 마이그레이션, 변환, 또는 포팅하는 것을 언급할 때 사용하세요.

npx skills add https://github.com/astronomer/agents --skill migrating-dagster-to-airflow

Dagster → Airflow 3 (Astro) migration

Migrate a Dagster project to Airflow 3 on Astro Runtime, honestly. The migration is asset-first (Dagster asset graphs translate to Airflow assets and asset-aware schedules, not flattened DAGs), incremental (domain by domain, Dagster stays authoritative until parity), and honest (every definition gets an explicit disposition; semantic deltas are documented, never papered over).

First time driving this? Read reference/quickstart.md first: hour-one commands, the glossary, and what can and cannot break.

Migration at a glance

  1. Baseline the source project's tests, then inventory it read-only (scripts/inventory.py → manifest).
  2. Review classifications (MECH/JUDG/REDESIGN/NONE per reference/mapping.md); make the go/no-go call (three outcomes; migrate-with-conditions is the common case, stay is the narrow one); plan DAG boundaries, per-edge IO decisions, and Gate 3 expectations into the manifest.
  3. Trial-migrate 2-3 representative units end-to-end through every validation gate.
  4. Migrate domain by domain through the six-gate ladder (reference/validation.md), tracking per-unit state (scripts/status.py); fix failure classes via reference/troubleshooting.md, never stub.
  5. Map the platform layer (secrets, alerts, CI/CD, Deployments) per reference/astro-deployment.md.
  6. Run side by side, then cut over per domain (consumers unpause first; see the checklist), keeping rollback one step away.
  7. Deliver the migration report: every definition dispositioned, an equivalence row per trigger, losses stated plainly.

Version drift

Verified against Airflow 3.3.0 / Astro Runtime 3.3-2 / astronomer-cosmos 1.15 / Dagster 1.13 (2026-07). Version-sensitive rows in the references carry their floor (notably the 3.2-vs-3.3 partition surface). Before relying on a version-gated claim: check the target (airflow version, astro deployment inspect), probe imports for sdk surface (python3 -c "from airflow.sdk import X"), and prefer --help / API spec discovery over assuming verbatim CLI/REST contracts on newer versions. Playbook entries are version-scoped per entry.

Requirements

  • Target Astro Runtime 3.3+ (Airflow 3.3+); the native asset-partition surface requires it. Below 3.2 the mapping degrades badly; say so and recommend upgrading before migrating.
  • The Dagster repo, and ideally a running Dagster instance (its materialization metadata provides parity-test fixtures).
  • astro CLI for the target project.

Hard rules

  1. Never stub. A translated unit either works through its validation gate or is deferred with a written reason. Fake-success bodies and workaround code with long justifying comments are failures.
  2. No silent omissions. Every record in the inventory manifest ends complete or deferred (reason). scripts/status.py summary exits nonzero otherwise; run it before claiming done.
  3. Equivalence rows for every trigger. Each schedule/sensor/automation condition gets a report row: source spelling, target spelling, delta in one sentence. Semantic deltas exist (catchup, on_cron inversion, eager guarantees); the sin is not the delta, it is the undocumented delta.
  4. Fix classes, not instances. When a translation pattern fails validation, fix the pattern (and record it in reference/troubleshooting.md), then re-apply; do not hand-patch one unit.
  5. Do not invent APIs. The references contain verified names only. Anything not covered there gets verified against official docs before use.

Workflow

Phase 0: Preflight

Confirm target Runtime version, astro CLI presence, and repo access. Detect the project layout: classic (@repository/workspace.yaml), modern (Definitions), or Components (pyproject.toml [tool.dg], defs.yaml files); all three occur, sometimes together. Baseline the source project's test suite now: pre-existing failures are recorded and excluded from migration blame.

Phase 1: Inventory (read-only)

python3 scripts/inventory.py <dagster_repo> --out manifest.json          # static scan
python3 scripts/inventory.py <dagster_repo> --runtime --out manifest.json # + runtime introspection when the project imports

The manifest lists every definition with file:line, captured params, current-vs-deprecated spelling, and dependency edges with their IO manager; every record starts classification: "pending". Classifying is YOUR first judgment task: assign each record MECH / JUDG / REDESIGN / NONE from its row in reference/mapping.md and write it into the manifest. The scanner enumerates (deterministic completeness); the agent classifies (judgment). A record you cannot map to a mapping.md row is itself a finding: record it, do not guess. Also grep for DAGSTER_CLOUD_ and EnvVar( (platform layer, Phase 5).

Manifest conventions: the canonical manifest lives in the migration run directory. Once the Astro project exists (Phase 2 scaffold), copy the manifest to its include/inventory/manifest.json so the Gate 3 pytest and status.py defaults find it; until then it just stays in the run dir (keep the two in sync afterward, the run-dir copy wins). Static records are the canonical migration units; runtime-mode enrichment merges into them, and only genuinely runtime-only definitions (factory-generated) become new units.

Emit the migration report skeleton now: one section per manifest record, plus the secrets/env naming map from reference/astro-deployment.md. Scale the skeleton to the project: a secretless local project gets a one-line "no secrets/platform layer" note, not empty boilerplate sections.

Phase 1.5: Go/no-go (the honest gate)

Before translating anything, answer the project-level question the inventory makes answerable: what does this team give up by migrating, and does each loss have an acceptable answer? Assess the NONE and REDESIGN rows against what is load-bearing for THIS team, evaluating the mitigation, not just the loss:

If load-bearingThe Airflow-world answerStay-signal only if
dbt rebuild-on-code-change (code_version_changed())State-aware dbt builds on a cron (Fusion / dbt State skip unchanged models per run, so the post-deploy tick rebuilds exactly what changed), and/or CI-triggered dbt build on merge (PR-gated, often an upgrade)The team can neither run a state-aware dbt stack nor dbt from CI
Freshness driving materializationAstro Observe freshness SLAs / Timeliness alerts + scheduled runs sized to the SLAFreshness-triggered compute is genuinely irreplaceable by schedule+alerting
Per-asset cost accounting (Insights)Astro Observe pipeline-level warehouse cost management; per-asset granularity is lostPer-ASSET chargeback is a contractual/organizational requirement
Asset catalog / column-level lineage as daily toolsAirflow 3 asset views + OpenLineage/Astro lineage (asset-level)Column-level lineage is embedded in daily workflows with no external catalog
Deep AutomationCondition compositions, can_subset, selective per-partition materializationMost decompose to cron/asset schedules (see reference/automation.md); the residue is redesigned per domainMultiple domains depend on compositions that decompose to nothing
Sensor cursor transactionality, run-scoped teardownIdempotent consumers + max_active_runs; context managers in task bodiesExactly-once event coalescing is a correctness requirement that idempotency cannot absorb

One rule the table implies, stated plainly: no dbt-only condition reaches "stay." Between Cosmos, state-aware dbt builds, and CI-triggered builds, every dbt-workflow loss has an accepted-practice mitigation (execution-proven in this skill's eval program, including on a real warehouse); dbt items are conditions to record, never blockers. The observability rows (per-asset cost, column-level lineage) are separate conditions and are evaluated on their own, even for dbt-heavy teams.

The gate's outcome is three-valued, and the middle one is the common case:

  • Migrate: no stay-signals; proceed to Phase 2.
  • Migrate with conditions (most real projects): losses exist, mitigations are named and accepted in writing in the report's first section, specific domains may carry REDESIGN work; proceed to Phase 2 with those conditions recorded.
  • Stay on Dagster, today: reserved for the case where MULTIPLE stay-signal conditions in the right column genuinely hold at once and the migration is not externally mandated. Then the honest deliverable is that recommendation, in writing, with the specific unmitigated losses named, and the run stops there. A migration guide that cannot say "don't" cannot be trusted when it says "do", but "don't" is earned by unmitigatable losses, not by the mere existence of deltas.

Phase 2: Plan

  • DAG boundaries: decide which asset-dependency edges become asset-aware schedules (cross-DAG) vs task ordering (intra-DAG). Group by domain/schedule cadence/team ownership; define_asset_job selections usually name the natural domains.
  • Per-edge IO decisions via the tree in reference/io-and-data-passing.md (fuse / explicit storage / XCom).
  • Order: leaf domains first, dependency order after; the platform layer last.
  • Fill each planned unit's target expectations into the manifest: dag_id, task_count, edges, schedule, asset_outlets per unit. Gate 3 asserts against exactly these fields; a unit without them is skipped by validation, so an unenriched manifest means Gate 3 checks nothing (validate_dag reports skipped counts loudly, do not ignore them).
  • Scaffold the target: astro dev init, shared helpers under include/. House conventions the scaffold imposes (e.g. a test demanding retries >= 2) do NOT override source fidelity: source behavior wins; convention adoption is a post-cutover improvement listed in the report, and the scaffold test gets skipped with an explicit reason.

Phase 3: Trial

Migrate 2-3 representative units end-to-end through every gate before fanning out. Pick one MECH asset, one partitioned asset, one JUDG case, or the nearest available mix (small projects may have no partitioned or no MECH assets; pick one full path through a real DAG instead). What the trial teaches goes into reference/troubleshooting.md before scaling; if the trial fails structurally, stop and rework the plan, not the units.

Phase 4: Migrate, domain by domain

Per unit, the state machine (tracked in the manifest):

pending → translate → fix-import → fix-lint → fix-tests → verify-parity → complete
                                    ↘ deferred (reason required)
  • Translate using the reference file for the construct (routing table below). Rich context beats cleverness: read the source unit, its mapping rows, and a nearby already-migrated example.
  • Validate through the gates: python3 scripts/validate_dag.py <astro_project> --manifest manifest.json (gates 1-3), then execution and parity per reference/validation.md.
  • On gate failure, retry with the latest validator output in context (cap ~10 attempts, then defer with the failure class).
  • Advance state only on gate pass: python3 scripts/status.py advance <unit-id> .... A wrong disposition is corrected with status.py reopen <unit-id> --reason .... The no-hand-editing rule applies to the STATE field (status) only; the PLAN fields (dag_id, task_count, edges, schedule, asset_outlets, target) are the planner's to write in Phase 2.
  • Units that deliberately translate to NO DAG of their own (helpers absorbed into tasks, policies that became alerts, resources that became connections) are dispositioned complete with target: "none" and evidence naming where they went; Gate 3 skips them by design.
  • Commit per unit, atomically.

Phase 5: Platform layer

reference/astro-deployment.md: Deployments topology, secrets/connection naming map, CI/CD and preview Deployments, alert-policy mapping, Observe/lineage expectations, the DAGSTER_CLOUD_* in-code rewrite checklist.

Phase 6: Side-by-side and cutover

Dagster remains authoritative. Run migrated DAGs shadowed/paused; compare outputs over the same logical window (row counts + checksums; recompute expected values from the Dagster-produced output itself, using recorded materialization metadata only opportunistically, per reference/validation.md Gate 5). Flip schedules one domain per change window: pause the Dagster schedule, unpause the Airflow DAG; rollback is the reverse. Keep Dagster readable after cutover (run history does not migrate).

Phase 7: Final report

scripts/status.py summary must pass. The report contains: the go/no-go assessment (Phase 1.5) and its rationale, disposition table for every definition, all equivalence rows, the NONE/REDESIGN losses stated plainly (lineage depth, code_version triggers, Insights cost accounting, sensor cursor transactionality), the secrets map, and the deferred list with reasons. Spot-check ten complete claims before delivering it.

Reference routing

Construct encounteredRead
First hour, glossary, what can breakreference/quickstart.md
Anything (first stop: one row per construct)reference/mapping.md
Asset-key → URI convention, translation granularity, external/observable assetsreference/assets.md
Asset deps, IO managers, XCom, storage decisionsreference/io-and-data-passing.md
Any partitions_def, partition mappings, backfillsreference/partitions.md
Schedules, sensors, AutomationCondition, freshnessreference/automation.md
@dbt_assets, translators, dbt Cloudreference/dbt.md
Components (defs.yaml), custom Component subclasses, dynamic generationreference/components.md
dagster_cloud.yaml, secrets, alerts, CI/CD, cutoverreference/astro-deployment.md
Gates, parity testing, state machinereference/validation.md
Failure classes seen beforereference/troubleshooting.md

Scripts

ScriptPurpose
scripts/inventory.pyScan the Dagster repo → JSON manifest (static + optional runtime mode)
scripts/validate_dag.pyGates 1-3 against the generated Astro project
scripts/status.pyPer-unit state machine + completeness gate

astronomer의 다른 스킬

airflow
astronomer
Apache Airflow DAG, 실행, 작업 및 시스템 구성을 쿼리, 관리 및 문제 해결합니다. DAG 검사, 실행 관리, 작업 로깅, 구성 쿼리 및 직접 REST API 액세스에 걸쳐 30개 이상의 명령을 지원합니다. 지속적인 구성으로 여러 Airflow 인스턴스를 관리하고 로컬 및 Astro 배포를 자동으로 검색합니다. DAG 실행을 동기식(완료 대기) 또는 비동기식으로 트리거하고, 실패를 진단하고, 재시도를 위해 실행을 지우고, 재시도/맵 인덱스 필터링을 통해 작업 로그에 액세스합니다. 출력...
official
airflow-hitl
astronomer
인간 승인 게이트, 폼 입력, 그리고 지연 가능 연산자를 사용한 Airflow DAG 내 분기 처리. 네 가지 연산자 유형: 승인/거부 결정을 위한 ApprovalOperator, 폼을 통한 다중 옵션 선택을 위한 HITLOperator, 인간 주도 작업 라우팅을 위한 HITLBranchOperator, 폼 데이터 수집을 위한 HITLEntryOperator. 모든 연산자는 지연 가능하며, Airflow UI의 Required Actions 탭 또는 REST API를 통해 인간 응답을 기다리는 동안 작업자 슬롯을 해제합니다. 선택적 기능 지원 포함: 사용자 정의...
official
airflow-state-store
astronomer
Persists task and asset state across retries and DAG runs using Airflow 3.3's AIP-103 key/value stores (`task_state_store`, `asset_state_store`) and the…
official
analyzing-data
astronomer
데이터 웨어하우스에 질의하여 캐시된 패턴과 개념 매핑을 통해 비즈니스 질문에 답변합니다. 반복되는 질문 유형에 대한 패턴 조회 및 캐싱을 지원하며, 결과 기록을 통해 향후 질의를 개선합니다. 개념-테이블 매핑 캐시와 INFORMATION_SCHEMA 또는 코드베이스 grep을 통한 테이블 스키마 탐색을 포함합니다. 분석을 위해 Polars 또는 Pandas DataFrame을 반환하는 run_sql() 및 run_sql_pandas() 커널 함수를 제공합니다. 개념, 패턴 및 테이블 캐시를 관리하기 위한 CLI 명령어와 추가 기능을 포함합니다.
official
annotating-task-lineage
astronomer
Airflow 태스크에 인렛과 아웃렛을 사용하여 데이터 계보를 주석 처리합니다. 입력 및 출력을 데이터베이스, 데이터 웨어하우스, 클라우드 스토리지 전반에 걸쳐 정의하기 위해 OpenLineage Dataset 객체, Airflow Assets 및 Airflow Datasets를 지원합니다. 운영자에 내장된 OpenLineage 추출기가 없는 경우 대체 수단으로 사용되며, 사용자 정의 추출기와 OpenLineage 메서드가 우선 적용되는 4단계 우선순위 시스템을 따릅니다. Snowflake, BigQuery, S3 및 PostgreSQL에 대한 일관된 명명을 보장하는 데이터셋 명명 헬퍼를 포함합니다.
official
authoring-dags
astronomer
Apache Airflow DAG 생성을 위한 안내 워크플로우로, 검증 및 테스트 통합을 포함합니다. 구조화된 6단계 접근 방식: 환경 및 기존 패턴 발견, DAG 구조 계획, 모범 사례에 따른 구현, af CLI 명령어로 검증, 사용자 동의 하에 테스트, 수정 반복. 발견을 위한 CLI 명령어(af config connections, af config providers, af dags list)와 검증을 위한 명령어(af dags errors, af dags get, af dags explore)는 DAG에 대한 즉각적인 피드백을 제공합니다...
official
authoring-go-sdk-tasks
astronomer
Writes Airflow task logic in Go using the Airflow Go SDK. Use when the user wants to implement Airflow tasks in Go, asks about `BundleProvider`/`RegisterDags`,…
official
authoring-java-sdk-tasks
astronomer
Airflow 작업 로직을 Java, Kotlin 또는 Airflow Java SDK를 사용하는 모든 JVM 언어로 작성합니다. 사용자가 Java/JVM에서 Airflow 작업을 구현하려 하거나, 요청할 때 사용합니다…
official