Week 15 - Final Project

Project Ideas and Inspiration

Cross-Track Collaboration

The Project Architecture

Ingestion, ACA, and CI/CD

dbt models

Publish to Postgres

Running the Pipeline Locally

Orchestrating with Airflow

Promoting to Production

Secrets & Variables


Optional: Failure Alerting

Optional: Enriching with an LLM

Optional: The Health Page

Optional: Inbound Sync

Team Timelines

Gotchas & Pitfalls

Frequently Asked Questions

Glossary: Week 15

Career relevance: Week 15

Orchestrating with Airflow

Running ingestion, dbt, and publish by hand proves your code logic. Running through Airflow proves orchestration logic: task order, container environment, and scheduler behaviour. Resource names follow the team-a example from Running the Pipeline Locally; substitute b or c on other teams. Astro-specific .env keys are in Secrets & Variables. Production is Promoting to Production.

How this chapter is structured

Same pattern as chapters 10 and 12: starter first, then your source.

Beat What you do Jump to
1. Starter Mode 1 then Mode 2 on the template pipeline (Arbeitnow image/job env is fine). One-time Astro setup → Mode 1 → Mode 2
2. Your source After Path A on your feed is green, re-run Mode 1 and Mode 2 on that code. Your source on Astro

Finish beat 1 before beat 2. ACA job URLs and VM Variables for a lasting swap are ch12, not this chapter.

When to run each beat

Goal When
Beat 1 (starter) After Path A starter is green; week 1 orchestration proof
Beat 2 (your source) After Path A beat 2 is green on your feed

By the end of this chapter, you should be able to:

Why Airflow after scripts are green

Airflow validates things the script-only flow cannot:

In short: scripts prove each component works; Airflow proves the full pipeline is runnable the way it will run in operations.

Where each task runs

Task Code Who actually runs it
ingest src/ingestion/pipeline.py in the pipeline image With INGEST_MODE=aca, Airflow starts the Container Apps job; the container executes the code. With INGEST_MODE=local, the worker runs Python directly.
dbt_build data/dbt project, mounted into the worker Airflow runs dbt build as a subprocess; Databricks executes the SQL.
publish_to_backend src/publishing/sync.py, mounted into the worker Airflow runs the Python script as a subprocess in the worker.

dbt build does not need its own container image because the worker only compiles SQL and submits it to your warehouse. ingest does, because it must run on a schedule even when your laptop is off, with its own dependency set isolated from Airflow.

https://gist.githack.com/lassebenni/3e3dfbf9bc857dc16e1a517c8d012198/raw/week_15__ingest_mode_visual.html

Mode 1 runs all three tasks inside the Astro worker. Mode 2 keeps ingest on a Container Apps job and leaves dbt_build and publish_to_backend on the worker. Modes 3 and 4 on the team VM also use ACA ingest (same idea as Mode 2); they differ by code source (main) and which landing / schemas they write.

Prerequisites

You need Path A green from Running the Pipeline Locally: ingest, dbt, and publish by hand, with rows in Railway analytics_dev.fct_postings. For beat 1 that is the starter feed; for beat 2 it is your feed. You also need:

One-time Astro setup

Dev-run credential for Astro

Path A on your laptop used az login for blob writes. Astro runs ingest inside Docker, where there is no interactive login. Add the dev-run service principal to data/.env before your first astro dev restart. The same principal uploads to the dev container in Mode 1 and starts job-fp-ingest-dev in Mode 2. Keep both vars in .env when you switch modes.

Fetch fp-devrun-client-id-team-x and fp-devrun-client-secret-team-x from the Orchestrating with Airflow matrix and set AZURE_CLIENT_ID and AZURE_CLIENT_SECRET (team-a names below):

az keyvault secret show --vault-name kv-hyf-data \
  --name fp-devrun-client-id-team-a --query value -o tsv
az keyvault secret show --vault-name kv-hyf-data \
  --name fp-devrun-client-secret-team-a --query value -o tsv

This is not the team's Databricks service principal. Do not use the Databricks SP secrets on your laptop.

Airflow settings template

Copy the settings template once so local Airflow defaults to INGEST_MODE=local:

cd data/airflow
cp airflow_settings.yaml.example airflow_settings.yaml

astro dev restart reads this file on every start. Chapter 10 does not need it; only Astro does.

Local Astro: .env vs airflow_settings.yaml

File What goes here locally In Admin → Variables?
data/.env Almost everything: Databricks, Azure, Postgres, LANDING_PATH, tokens, dev-run SP No; Astro mounts this into worker containers
data/airflow/airflow_settings.yaml INGEST_MODE only (local or aca); room for Connections/Pools later Yes; Astro re-seeds listed Variables on every astro dev restart

Path A (uv run, preflight) and Astro share data/.env. That is why credentials and resource names stay there instead of the yaml: one file for scripts and DAG tasks alike. Secrets in the DAG use the environment (then Key Vault on the VM), not Airflow Variables; copying the prod VM Variable list into yaml would not help and would duplicate .env.

INGEST_MODE alone belongs in yaml because it is an orchestration toggle (like prod), not a credential; it is not in .env.example; and Astro re-applies yaml on every restart so Mode 1 stays the default after cp …example.

Skip the yaml copy → Mode 2 by default. If you never copy airflow_settings.yaml.example, nothing seeds INGEST_MODE=local. The DAG then falls back to aca: ingest tries to start a Container Apps job and needs extra Azure settings you have not wired yet. Copy the template before your first Astro run.

<aside> 💡 One Variable in the UI is normal. After restart you usually see only INGEST_MODE. For every other name in Secrets & Variables, check .env first; those keys are not Airflow Variables locally. To switch modes, edit variable_value in yaml and astro dev restart; a Variable seeded from yaml overrides the same name in .env if both exist.

Prod VM is different: most settings are Airflow Variables at provisioning (Promoting to Production). Do not mirror that list into local yaml; fill .env instead.

</aside>

Mode 1: local Python ingest in Astro

Use this mode first. The DAG orchestrates all three tasks, but ingest runs local Python inside the Airflow worker instead of triggering ACA.

Start Astro

cd data/airflow

# Forces a full recreate so code, env vars, and airflow_settings.yaml reload.
astro dev restart

Use the URL astro dev restart prints, not a URL from an old session. See Gotchas if the port looks wrong.

astro dev restart seeds INGEST_MODE=local from airflow_settings.yaml (see one-time setup above).

Trigger the DAG

The Astro UI is the same one you used in Week 12: Airflow fundamentals. Your DAG is final_project_pipeline with three tasks (ingest → dbt_build → publish_to_backend), not hello_pipeline with one.

Clicking the play button to trigger a DAG in Astro (Week 12)

Clicking the play button to trigger a DAG in Astro (Week 12)

  1. Open DAGs and find final_project_pipeline
  2. Toggle from Paused to Unpaused
  3. Open Graph view and click Trigger DAG (or run tasks one by one: ingest → dbt_build → publish_to_backend)
  4. Wait for each task to turn green before starting the next

When the run finishes, the grid should look like Week 12's green column: every task cell dark green:

Astro grid view after a successful DAG run (Week 12 hello_pipeline example)

Astro grid view after a successful DAG run (Week 12 hello_pipeline example)

Validate

Task instance logs in Astro (Week 12 example)

Task instance logs in Astro (Week 12 example)

select count(*) as rows,
       count(distinct posting_id) as ids,
       max(ingested_at) as latest
from analytics_dev.fct_postings;

Mode 1 exit criteria: DAG run succeeds with INGEST_MODE=local, and Railway Postgres shows fresh rows in analytics_dev.fct_postings (same host as BACKEND_PG_HOST / fp-pg-host-team-x from Key Vault, not a local container).

Mode 2: ACA-backed ingest

Mode 2 starts the dev Container Apps job from Astro instead of local Python. After Mode 1 is green, switch the same DAG to ACA-backed ingest for full integration validation. Astro is already running. You only need one more restart after editing .env and airflow_settings.yaml.

<aside> 💡 DE2 platform seat, week 3+. Every data trainee should read this section. Only the platform owner needs to run the image build and job update commands.

</aside>

Mode 2 does not build or deploy on merge to your branch. The ingest task starts an existing Container Apps job and waits for it (POST …/start with an empty body). Astro does not pass your laptop SOURCE_API_URL (or other ingest env) into that start. The container always uses job env in Azure. Why, and how CI rewrites that env: Ingestion, ACA, and CI/CD. Build, push, and point the dev job at your image by hand first, or a green ingest task only proves the old image still works. A green Mode 2 run with a FreeHire .env can still be fetching Arbeitnow if the job env was never updated. After Path A is green on your source, Promoting to Production is how merge rewrites both ACA jobs; for a Mode 2 spike before merge, update the dev job image (and URL if you must) by hand as in the steps below.

The dev-run AZURE_CLIENT_* keys from one-time setup are what let the worker start job-fp-ingest-dev. Leave them in .env when you switch back to Mode 1.

Step 0: build, push, and verify the dev image