Week 14 - Infrastructure as Code
Why use Infrastructure as Code
You already deployed Bicep from your laptop with az deployment group what-if and az deployment group create. Professional teams use CI/CD for infrastructure the same way Week 6 used it for a containerized app on Azure. In this chapter, you will run the same Bicep commands from GitHub Actions so every pull request gets a preview, and every merge to main can apply the template.
This chapter is the CI/CD pipeline for infrastructure: in Python CI Pipeline the pipeline built and pushed a Docker image. Here the artifact under review is the desired Azure state in Bicep.
By the end of this chapter, you should be able to:
what-if), then apply)on / jobs / steps) onto Bicep what-if and createAZURE_CREDENTIALS from Key Vault, and commit a Bicep GitHub Actions workflowwhat-if job is worth running (after your hand-in Bicep exists), and what still stays on the laptop (teardown)| Week 5 (containers) | This chapter (Bicep) |
|---|---|
| Lint / test | Optional: az bicep build (compile check) |
docker build |
az deployment group what-if on a pull request |
| Push image to ACR on green build | az deployment group create on merge to main |
AZURE_CREDENTIALS + azure/login |
Same login pattern |
You are not learning a new Azure API. You are putting the Chapter 3–4 commands on a runner.
https://gist.githack.com/lassebenni/df480bc30f53223c41b581944106332d/raw/week_14__bicep_ci_preview_apply_visual.html
az login credentialsWeek 5 already covered the mechanism. You store a service principal (a machine identity a workflow signs in as, instead of a person) as AZURE_CREDENTIALS (the GitHub secret holding that identity's login JSON), then call azure/login from Actions (Setting up Azure credentials for CI). Reuse that habit here.
What is new is the permission scope. The Week 5 principal was for AcrPush on hyfregistry. This chapter uses a different principal with deploy rights on $CLASS_RG (HYF Data Track Student IaC on rg-hyf-students). Fetch that JSON from Key Vault yourself: same self-serve pattern as postgres-url-<name> and airflow-ui-password-<name>.
az login
az keyvault secret show \
--vault-name kv-hyf-data \
--name azure-credentials-week14-<yourname> \
--query value -o tsv
<yourname> is the same Key Vault role suffix you already use for postgres-url-<name> (Weeks 9–12). Or open the kv-hyf-data secrets page, sign in with your HYF account, and show azure-credentials-week14-<yourname>.
Do not reuse the Week 5 ACR AZURE_CREDENTIALS value for Bicep deploys. Your Entra group membership covers laptop deploys; the Actions runner still needs this SP JSON in a GitHub repository secret on the graded hand-in repo. Keep azure-bicep-reference for local Chapters 3–4 only. Do not add Actions secrets there.
AZURE_CREDENTIALSStep 1: Open c55-data-week-14 while signed into your GitHub account.
Step 2: Click Fork → create the fork under your account (default options are fine). If you already forked it earlier this week, open your fork instead (github.com/<you>/c55-data-week-14).
Step 3: Fetch the Week 14 JSON from Key Vault (command above). Copy the whole JSON block. Do not commit it to any file.
Step 4: On your fork, open Settings → Secrets and variables → Actions. You should land on the Actions secrets page. Click New repository secret.

GitHub Settings → Secrets and variables → Actions on a c55-data-week-14 fork: empty Repository secrets (“This repository has no secrets”) and the New repository secret button. Your fork path is github.com/you/… instead of lassebenni.
Step 5: Name the secret (AZURE_CREDENTIALS). Paste the full JSON into the Secret field, then click Add secret. Do not screenshot or commit the real JSON.
Paste the JSON exactly as Key Vault returned it, outer curly braces included. Do not trim it. The block has about ten fields, and azure/login reads four of them: clientId, clientSecret, subscriptionId and tenantId. The rest are endpoint URLs for other Azure clouds, which the action ignores, so leaving them in costs nothing and editing the JSON by hand is the easiest way to break the login.

New repository secret form with Name AZURE_CREDENTIALS and a redacted placeholder JSON value (clientId, clientSecret, subscriptionId, tenantId). Paste your real Key Vault JSON; never commit it.
After you save, the Repository secrets list shows AZURE_CREDENTIALS by name only. GitHub never shows the value again.
<aside> 🤓 Curious Geek: why GitHub never shows the secret again
After you click Add secret, GitHub stores the value encrypted at rest and only decrypts it inside a job that references secrets.AZURE_CREDENTIALS. The UI keeps the name visible and hides the value forever, so a leaked Settings screenshot cannot steal the service principal. If you mistype the JSON, you overwrite the secret with a fresh paste from Key Vault instead of editing it in place.
</aside>
You will hand in the assignment from this same fork. Planting the secret now is enough for this chapter; a green Actions what-if waits until your Bicep files are filled in (assignment Task 1 or practice). Without the secret, later azure/login steps never reach az.
<aside>
💡 Recap: Same login pattern as Week 5; self-serve the Week 14 JSON from Key Vault into your c55-data-week-14 fork. ACR push rights ≠ IaC deploy rights on $CLASS_RG.
</aside>
<aside>
📘 Recap from Week 5: A workflow file lives in .github/workflows/. on: chooses when it runs, jobs: are machines, steps: are the commands. Secrets come from GitHub Secrets, never from committed files. Refresh: Python CI Pipeline and CI pipeline (automated checks that run on every push).
</aside>
Chapters 3–4 stay in a clone of azure-bicep-reference: you run az on your laptop, so that repo never needs GitHub Actions. CI is different. A workflow and AZURE_CREDENTIALS live under Settings → Secrets and variables → Actions, which only works on a repository you own.
The reference repo is the teacher's shared teaching material (and it holds public -solution branches). Do not put cohort secrets or hand-in evidence there. Your fork of c55-data-week-14 is the graded home: it already starts with a Chapter 4–complete Bicep starter (storage + nested raw), plus room for bicep.yml. Add the workflow file now; extend the starter later in the Assignment (environment tags + curated container). Do not replace the starter with a paste from a -solution branch.
Only the parts that matter for this chapter are shown; your fork also ships README.md, WRITEUP.md, AI_ASSIST.md, docs/, .hyf/, and a devcontainer, which the assignment uses.
c55-data-week-14/ # your fork (github.com/<you>/c55-data-week-14)
├── main.bicep # Ch4-complete starter (extend in the assignment)
├── modules/
│ └── storage.bicep # storage + nested raw (extend with curated later)
└── .github/
└── workflows/
├── grade-assignment.yml # already there (autograder)
├── pr-body-check.yml # already there (checks your PR description)
└── bicep.yml # you add this now
AZURE_CREDENTIALS is not a file in the tree: it lives under the fork's Settings → Secrets and variables → Actions. Your unique sthyf… storage name is plain config: put it in the workflow --parameters line (below).
This chapter's required skill is committing bicep.yml. A live green what-if job needs your extended hand-in template; that comes after Assignment Tasks 1–3 (or practice) once environment tags and curated are in place.
Create .github/workflows/bicep.yml like this:
name: Bicep
on:
pull_request:
push:
branches: ["main"]
env:
CLASS_RG: rg-hyf-students
jobs:
what-if:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Preview deployment
run: |
az deployment group what-if \
--resource-group "$CLASS_RG" \
--template-file main.bicep \
--parameters \
storageName=sthyfyourname \
dbAdminPassword=not-a-real-secret
deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Apply deployment
run: |
az deployment group create \
--resource-group "$CLASS_RG" \
--template-file main.bicep \
--parameters \
storageName=sthyfyourname \
dbAdminPassword=not-a-real-secret
Replace sthyfyourname with your unique storage account name (same rule as Chapters 3–4). The starter template still declares a dummy @secure() param dbAdminPassword. Pass a throwaway value in the workflow for class (as above). Do not put a real password in YAML.
storageName in YAML, but keep credentials as a secret?Prefer dropping the unused @secure() param before you automate apply. Until then, the throwaway dbAdminPassword=not-a-real-secret in the sample is enough for class: never put a real password in YAML.
https://gist.githack.com/lassebenni/3bf8a07d15a4f5794acfe4b5b2220475/raw/week_14__gha_bicep_whatif_pr_terminal.html
The recording streams the real Actions job log from a PR: azure/login@v2, then az deployment group what-if with + Create / * Ignore lines. Open the what-if job on your PR and read the same stream before you merge.
<aside>
⚠️ A green what-if on a PR is not permission to skip reading the output. Reviewers (including you) still read the + / ~ lines. CI makes the preview automatic; judgement stays human.
</aside>
Treat the Actions log like a teammate's what-if paste, not a green checkbox alone.
If that feels like too much digging, it is: teams usually post the preview straight onto the pull request so a reviewer sees it without opening the log. Advanced Bicep has the ten extra lines, using gh and no third-party action.
One detail decides whether your workflow can run at all: which repository the pull request targets.
<aside>
⚠️ Set the base repository to your own fork. When you push a branch, GitHub offers a Compare & pull request button whose base defaults to the cohort repo, HackYourAssignment/c55-data-week-14. GitHub never passes repository secrets to a pull request opened from a fork, so AZURE_CREDENTIALS is empty there and azure/login fails. On the compare page, change base repository to your own fork before you create the PR.
</aside>
The base-repository dropdown sits at the top left of the compare page, next to the branch selectors. You still hand in the assignment with a PR to the cohort repo later; that one is for review, not for running your workflow.
<aside>
⌨️ Hands on: In your c55-data-week-14 fork, add the workflow file .github/workflows/bicep.yml as above, then open a pull request from your branch into your own fork's main. Walk the Actions UI the same way as the screenshots below. A live green run is optional until you extend and deploy the starter in the assignment (Tasks 1–3); use Assignment Task 6 for that bonus evidence.
</aside>
Step 1: Open the PR, then open the Bicep workflow run (from the Checks tab or the Actions tab). You should see the what-if job succeed and deploy skipped on a pull request.

GitHub Actions run summary for bicep.yml on a pull request: what-if succeeded, deploy skipped. Your run title will differ; look for the same two jobs.
Step 2: Click the what-if job. Confirm the step list includes azure/login@v2 and the Preview deployment step.

GitHub Actions what-if job step list with azure/login@v2 and Preview deployment succeeded. Open the same job on your PR.
Step 3: Expand Preview deployment and read the log. Look for + Create (or = / ~) lines for your storage template before you merge.

Expanded Preview deployment log showing + Create for a storage account and blob service under rg-hyf-students. Your storage name will differ; read the same + / ~ / * symbols.
You now have the same review-then-apply workflow shape as Week 5, aimed at Azure state instead of a Docker image. The runner will use it once your extended hand-in template exists.
<aside>
💡 Recap: Commit bicep.yml on the assignment fork (with your storageName=), then confirm PR what-if in Actions. Merge create runs for real after your Bicep is filled in. Live jobs also need AZURE_CREDENTIALS.
</aside>
az resource delete on your storage account. The workflow above never deletes. Removing a resource from Bicep still does not delete it in Azure.echo credentials; rotate AZURE_CREDENTIALS if it leaks.https://lasse.be/simple-hyf-teach-widget/mcq.html?bank=week_14_ch5_bicep_cicd_quiz&embed=1
az deployment group what-if on a pull request?AZURE_CREDENTIALS instead of az login?sthyf… storage name live for the sample workflow?If the Actions plus Bicep wiring still feels unclear, Microsoft's own walkthrough builds the same workflow from scratch. It runs 90 minutes, so it sits on Advanced Bicep rather than here.
If you prefer a draft before you tune YAML by hand, an LLM can help; treat its output as a starting point.
<aside>
💡 Using AI to help: Paste a redacted copy of your local az deployment group what-if / create commands (no secrets) and ask an LLM to draft a GitHub Actions workflow that runs what-if on pull_request and create on push to main. Diff the result against the sample above before you commit. (⚠️ no real data, no PII)
</aside>