You are a Data Engineer at Rootsmarkt, a grocery chain with twelve stores.
Every night, a supplier platform called SupplyHub delivers the day's sales
file. A pipeline picks it up, cleans it, aggregates it, and loads
daily_revenue. At 07:00 Finance opens a dashboard built on that table.
On 2026-03-01 the dashboard showed €0.00 for every store.
The pipeline log was entirely green.
Over the next four hours you will rebuild that pipeline twice — once in Apache Airflow, once in Dagster — and work out what the system should have known.
Do this days before the course, not on the morning. If something is going to go wrong, it should go wrong while there is time to fix it. Budget 30 minutes, most of it waiting for a Docker image.
Six steps, in this order. Each one tells you how to know it worked.
Windows: run everything in PowerShell — not in WSL, not in Git Bash. The commands below are identical on both platforms; they are program invocations, not shell syntax.
WINDOWS-SETUP.mdhas the full walkthrough.
Run the setup commands from the repository directory:
cd orchestration-rootsacademy-2026You need three things: Docker Desktop, uv, and the Astro CLI.
macOS
brew install --cask docker # then OPEN it, and leave it running
brew install uvThen the Astro CLI. Download it directly — the version is pinned for this course, and the download is the only way to get exactly it:
curl -sSLo /tmp/astro.tgz https://github.com/astronomer/astro-cli/releases/download/v1.42.1/astro_1.42.1_darwin_arm64.tar.gz
shasum -a 256 /tmp/astro.tgz
tar -xzf /tmp/astro.tgz -C /tmp astro
sudo mv /tmp/astro /usr/local/bin/astroThe shasum line must print:
87c25a9652b420f47c067a80671cdf48bfc6607f2dc13d198a6f75addc34c026
On an Intel Mac use astro_1.42.1_darwin_amd64.tar.gz instead, and expect
5346b505c5bb63dbe96092cc385716cc3f0106519df3c8e49e6b647498c6366d.
No sudo? Put it somewhere you own instead — mkdir -p ~/.local/bin && mv /tmp/astro ~/.local/bin/
— and make sure that directory is on your PATH.
Homebrew works too, but read this first:
brew install astronomer/tap/astro@1.42.1also installs Podman, because the formula recommends it. That is harmless — the Astro CLI looks fordockerbeforepodman— but it is ~100MB you did not ask for. Do not add--without-podman: current Homebrew rejects it outright withinvalid option. And do not use plainbrew install astro, which is a different, unpinned version.
Then pull the Airflow image now rather than on the day:
docker pull astrocrpublic.azurecr.io/runtime:3.3-2Windows (PowerShell) — no winget and no admin rights needed, except for
one step:
-
Enable WSL 2. This is the only part needing administrator rights, and it is one-time per machine. In an admin PowerShell, then reboot:
wsl --update wsl --install --no-distribution
If you cannot elevate, ask IT now — nothing else needs admin, but nothing works without this.
-
Install Docker Desktop, per-user, no admin: download it, then either double-click it or run
& "$HOME\Downloads\Docker Desktop Installer.exe" install --user. Open it and leave it running. -
From the repository directory, run:
powershell -ExecutionPolicy Bypass -File .\setup-windows.ps1
It installs uv, downloads the pinned Astro CLI, verifies its checksum against Astronomer's published value, puts it on your PATH and pulls the Airflow image. Then open a new terminal so the PATH change applies.
-ExecutionPolicy Bypassapplies to that one run only. It is there because managed laptops block scripts by default.
If scripts are blocked outright — common on managed laptops — the same steps by
hand are in WINDOWS-SETUP.md, along with the winget
shortcut if you happen to have it.
✓ Done when all three report a version:
docker info --format "{{.ServerVersion}}" # a number, not an error
uv --version
astro version # 1.42.1If it fails
astro versionreports something other than 1.42.1 → on macOS you have a different Astro on yourPATH.which astrotells you where it is; the download above puts 1.42.1 in/usr/local/bin.astronot found on Windows → you did not open a new terminal after the script added it to PATH.docker infoerrors → Docker Desktop is installed but not running. Open it.wslerrors on Windows → Step 1.1 has not been done, and it needs admin.
uv syncThis creates .venv/ and installs Dagster, the roots CLI and the sealed
processing package. It does not install Airflow — that lives only inside the
Docker image, deliberately.
✓ Done when
uv run roots --helplists commands including join, doctor, verify and hint.
If it fails — a resolution error here usually means a stale .venv. Delete
it and re-run.
Your instructor gives you a join code — six characters, like RMEETP.
uv run roots join RMEETPYou do not create .env yourself. This command creates both files you need:
| File | What it holds | Committed? |
|---|---|---|
config/team.yaml |
your store roster, plausible revenue bounds, warehouse credentials, SupplyHub token | No — it has secrets |
.env |
the same values as environment variables, which is what Airflow and Dagster read | No — same reason |
They hold the same information in two shapes because two different runtimes need
it two different ways. Never edit either by hand — if something is wrong, run
roots join again.
If your instructor also gave you a lab address, add it:
uv run roots join RMEETP --lab-url http://192.168.1.50:8090Usually you will not need to — the address is normally already built into the repository you cloned.
✓ Done when config/team.yaml and .env both exist, and the command printed
joined as team-NN followed by an Airflow connection recipe.
If it fails
could not join→ the lab server is unreachable. Check the address with your instructor; you may need--lab-url.401ortoken does not match→ the join code was mistyped. They are case-insensitive but otherwise exact.
uv run --env-file .env roots doctorNote the --env-file .env, and use it from now on. Step 4 created that
file; every command after this point needs the values in it, and that flag is
what loads them. Forget it and things fail confusingly — Dagster loses its event
log, the warehouse becomes unreachable. Just always include it.
roots doctor checks versions, Docker, ports, both runtimes, the lab server and
the warehouse, and tells you exactly what to fix.
✓ Done when there are no red FAIL lines. Yellow .... lines are fine.
Do not go to Step 6 until this is green. This is the entire reason for doing setup early.
Three things run at once, so you need three terminal windows, all in the repository directory. Leave all three running for the whole course.
Terminal 1 — Airflow
astro dev startThe first run takes a few minutes; later ones are fast. Read the URL it
prints — usually http://localhost:8080, but Astro picks a different port when
8080 is busy. Log in with admin / admin.
Terminal 2 — Dagster
uv run --env-file .env dg dev --target-path dagsterOpens on http://localhost:3000. The asset graph will be empty. That is correct — building it is mission D1.
Terminal 3 — the progress daemon
uv run --env-file .env roots watchRe-checks your work every 20 seconds and updates the instructor's dashboard. Start it and forget it.
✓ Done when both UIs load in a browser and roots watch is printing.
uv run --env-file .env roots airflow-conn # the values mission A1 needsThen open missions/A1.md and start.
CONCEPTS.md Mental models for Airflow and Dagster. Keep it open.
missions/ START HERE. A1 -> A5, then D1 -> D4.
dags/ Your Airflow pipeline. You edit this.
dagster/src/rootsmarkt/defs/
Your Dagster assets. You edit this.
packages/rootsmarkt-processing/
SEALED. All the data processing, written and tested.
Read it freely; you should not need to change it.
config/team.yaml Your team's roster, credentials, revenue bounds.
data/ Deliveries you have fetched, and cleaned output.
hints/ checkpoints/ Read via `roots hint` and `roots checkpoint`.
Data processing reads, transforms, validates, aggregates and writes data.
It is already written for you, in packages/rootsmarkt-processing/.
Orchestration decides when something runs, represents dependencies, waits for conditions, coordinates systems, retries work, records execution state, reacts to failures, and exposes what happened.
That is what you will write.
Every time you add a line today, you should be able to say which of the two it is. Some lines are genuinely on the boundary — those are the interesting ones, and the missions point them out.
You are not expected to memorise Airflow or Dagster syntax. You are expected to leave able to say "this is orchestration because…" and "I would represent this differently in Airflow and Dagster because…".
All of these take the uv run --env-file .env prefix.
roots verify a2 # check one mission now, and report it
roots status # what this machine thinks you have earned
roots hint a2 # next hint. Run again for the one after.
roots checkpoint a2 # take a known-good state and keep moving
roots submit a2 ... # evidence submissions (A2, D3, D4)
roots airflow-conn # the exact Airflow connection values for A1
roots doctor # when something is wrong and you do not know what
roots online --lab-url … # if the lab moves. Your instructor gives you the address.Use hint and checkpoint freely. There is no penalty and nobody to ask.
Losing forty minutes to one mission costs you the rest of the course, which is a
much worse outcome than taking a hint.
Row counts, delivery IDs, store rosters and the plausible revenue range all differ per team, derived from your team ID.
If your numbers do not match the team beside you, nothing is broken. You are both right. It also means a copied answer will not verify — not because anyone is policing it, but because it is literally about different data.
roots doctor first, always. It names the fix.
| Symptom | Cause |
|---|---|
| Dagster materializations vanish between runs | You dropped --env-file .env, so DAGSTER_HOME was not set |
password authentication failed for user "team_NN" |
Your credentials are fine — something else is on that port. roots doctor explains |
| Airflow UI not on 8080 | Astro picked another port. Read what astro dev start printed |
| A DAG stopped appearing | It failed to parse. astro dev pytest gives a readable error |
roots verify takes ~30s the first time |
Expected. It reads your Airflow run history, and each read is a container call. Later runs take a few seconds — the finished runs are cached |
| Mission A1 cannot reach SupplyHub | roots doctor checks that address separately — it is not the same one as the dashboard |
If the lab server becomes unreachable, your work is not lost: milestones are
recorded locally in .roots/ and sent when it comes back. If your instructor
moves the lab to another machine, they will give you an address:
uv run roots online --lab-url http://<address>:8090
uv run --env-file .env roots doctorEverything you have already fetched and loaded stays valid — the delivery data is identical whichever machine serves it.