Optional: Enriching with an LLM
Orchestrating with Airflow got Mode 1 and Mode 2 green on your laptop. This chapter is what happens after that on the team Airflow VM and in Azure: Mode 3 (platform-dev on main) and Mode 4 (prod).
A merge to main updates three things: the ingestion image, the prod Container Apps job, and the DAG on the team Airflow VM. Promoting is not a separate deploy step you run from your laptop. It is opening a pull request, getting it reviewed, and merging. What changes in production depends on which files your PR touched.
<aside>
📘 Recap from Week 5: merge-to-main CI is the same GitHub Actions pattern from Python CI pipeline. Here build and deploy-prod run only on merge, not on every pull request.
</aside>
Four Airflow targets (plus Path A with no Airflow). Each green run proves something the previous one could not. Landing and dbt schema are separate: Mode 2 and Mode 3 share aca-dev landing, but only Mode 3 writes the shared dev_airflow schema.
| Path A (laptop, no Airflow) | Mode 1 (Astro + local ingest) | Mode 2 (Astro + ACA-dev) | Mode 3 (VM dev DAG) | Mode 4 (VM prod DAG) | |
|---|---|---|---|---|---|
| Where | laptop uv run |
Astro | Astro | team VM | team VM |
| Ingest | local Python | local in worker (INGEST_MODE=local) |
job-fp-ingest-dev |
job-fp-ingest-dev |
job-fp-ingest |
| Landing | personal dev/<you> |
personal (LANDING_PREFIX) |
aca-dev |
aca-dev |
prod |
| dbt schema | dev_<you> (your branch) |
dev_<you> (your branch) |
dev_<you> (your branch; dbt on laptop) |
dev_airflow (main) |
analytics (main) |
| Publish → Postgres | analytics_dev |
analytics_dev |
analytics_dev |
analytics_dev |
analytics |
| Green run proves | ingest + dbt + publish without Airflow | same chain under the DAG from your branch | dev ACA image and job env work | full path on main without prod schemas |
daily delivery the backend can trust |
Mode 1 and Mode 2 set INGEST_MODE in airflow_settings.yaml (not .env) and restart Astro. Mode 2 also points .env LANDING_PATH at the aca-dev parent (team A: …/aca-dev/events). Mode 2 does not auto-run your uncommitted branch in ACA: only whatever image and job env Azure already has.
Mode 3 and Mode 4 both run on the team VM from main (git pull about every minute after merge). Both schedule at 09:00 Europe/Amsterdam. Mode 3 is created paused: unpause it once so the morning smoke runs. Mode 4 is the prod pipeline you monitor every weekday (optional Slack alerts: Failure Alerting). Your laptop cannot write Mode 4 schemas or the prod landing volume.
<aside>
💡 Mode 2 and Mode 3 share aca-dev landing and job-fp-ingest-dev, but Mode 2 still builds into your personal dev_<you> schema. Only Mode 3 uses the shared dev_airflow schema.
</aside>
Same pattern as chapters 10 and 11: starter first, then your source.
| Beat | What you do | Jump to |
|---|---|---|
| 1. Starter | Trigger the unchanged Mode 4 baseline on the VM; learn what a merge updates. | Run the prod baseline unchanged → What merge actually changes |
| 2. Your source | After Path A and Astro beat 2 are green, ship workflow literals, verify ACA job env, set VM Variables, prove Mode 4 (and Mode 3). | When you swap the source API → Airflow VM Variables |
Finish beat 1 before beat 2. Ingest and dbt code changes: stage chapters + ch10 beat 2. How data-ci-cd.yaml works: Ingestion, ACA, and CI/CD. Astro Mode 1/2 on that code: ch11.
By the end of this chapter, you should be able to:
analyticsmain for ingestion code vs DAG-only vs dbt-only changes (detail in the ingestion chapter)SOURCE_API_URL literal and verify job env after mergeLANDING_PATH / LANDING_PATH_DEV) so Mode 4 and Mode 3 match Mode 2 locallyanalytics) has fresh rows after a scheduled runbuild and deploy-prod skip on pull requests but run on mergeDo this before your first feature pull request. You do not have to write any code to see the prod pipeline work. A starter version of every part has already been deployed for your team.
This section uses your team's prod Airflow VM in the browser, not Astro on your laptop. Mode 1 and Mode 2 in the previous chapter prove the DAG on your machine; Mode 4 proves the same DAG on the schedule that writes analytics. VM-only credentials (Airflow UI password, service principal, prod Postgres secret) are in Secrets & Variables.
| Team | Prod Airflow |
|---|---|
| A | https://vm-hyf-team-a.westeurope.cloudapp.azure.com |
| B | https://vm-hyf-team-b.westeurope.cloudapp.azure.com |
| C | https://vm-hyf-team-c.westeurope.cloudapp.azure.com |
Step 1: Open your team's prod Airflow UI from the table above.
Step 2: Sign in to the prod Airflow UI:
student-<your-name> (same <your-name> as in your Key Vault secret; see Airflow UI login)fp-airflow-password-<your-name> from Key Vault (not the Postgres password):az keyvault secret show --vault-name kv-hyf-data \
--name fp-airflow-password-<your-name> --query value -o tsv
Step 3: Find the final_project_pipeline DAG. Unpause it if needed, open Graph view, and click Trigger DAG.
Step 4: Wait until ingest → dbt_build → publish_to_backend are all green.

Prod Airflow overview for final_project_pipeline with recent successful runs and the task graph
<aside>
💡 Your DAG may show an extra enrich task if your team added it; the three core tasks above are what the starter baseline must green.
</aside>
Step 5: Run the same shape of query against the prod schema:
select count(*) as rows,
count(distinct posting_id) as ids,
max(ingested_at) as latest
from analytics.fct_postings;
<aside> 💡 Prod resource names differ from dev on purpose. The table in Running the Pipeline Locally is the map. Never point CLI commands or browser tabs at another team's URL by mistake.
</aside>
If the baseline run does not go green, or Postgres stays empty after a "successful" run, stop and tell a teacher which task failed and what the task log says. That is a platform issue, not a student exercise.
<aside>
💡 Recap: both baselines now exist. Dev runs on your laptop into analytics_dev, prod runs on your team's Airflow VM into analytics, and you have seen rows in each. From here every change you make is measured against these two known-good runs.
</aside>
Verified against the team repos' data-ci-cd.yaml workflow. Job-by-job walkthrough: data-ci-cd.yaml walkthrough.
| What you changed | What runs on merge to main |
What students should check |
|---|---|---|
Ingestion code (data/src/…, Dockerfile) |
build pushes pipeline:<commit-sha> to ACR (Week 5: Azure Container Registry); deploy-prod / deploy-dev update job-fp-ingest and job-fp-ingest-dev (Week 6: Container Apps Jobs), including job env vars such as SOURCE_API_URL |
GitHub Actions run green; job image tag matches merge SHA; job SOURCE_API_URL is your source, not the starter |
DAG or Python in data/airflow or data/src |
Team monorepo main reaches the prod Airflow VM via git pull within about a minute (same repo students clone) |
Prod UI shows updated DAG; next run uses new code |
| dbt models only | Same git pull on the VM; build may still run but ingestion image is unchanged |
Next prod dbt_build task output; row counts in analytics |
Schedule in pipeline_dag.py |
git pull only; no image rebuild needed |
DAG Next Run time in prod UI |
Mode 4 runs the same code as your laptop, as different identities. What changes on promotion:
| On your laptop / Mode 1–2 | On Mode 4 (prod VM) |
|---|---|
| Astro (Docker) | Team Airflow VM |
dev container (+ your prefix or aca-dev) |
prod container |
Databricks dev_<yourname> (or Mode 3 dev_airflow) |
analytics (and related prod schemas) |
Postgres analytics_dev |
Postgres analytics (same table names) |
data/.env |
Airflow Variables + Key Vault |
flowchart LR
merge[Merge to main]
build[CI build job]
dprod[CI deploy-prod]
ddev[CI deploy-dev]
acr[ACR pipeline:sha]
aca[job-fp-ingest]
acadev[job-fp-ingest-dev]
vm[Airflow VM git pull]
dag[final_project_pipeline]
merge --> build --> acr
acr --> dprod --> aca
acr --> ddev --> acadev
merge --> vm --> dag
<aside> 💡 DAG green before your custom dbt is done is fine. Mode 1 on the starter pipeline proves orchestration; you can swap models in later without blocking the chain.
</aside>
Keep the merge diagram above next to you while you promote: it is the map for everything in this chapter.
<aside> 💡 Editable deep-dive template (optional): week_15__architecture.excalidraw. Prefer a mermaid diagram in your team README.
</aside>