Week 14 - Infrastructure as Code

Why use Infrastructure as Code

IaC concepts

Azure Bicep

Bicep in practice

Deploy Bicep from CI/CD

Practice

Assignment

Gotchas & Pitfalls

Glossary: Week 14

Career relevance: Week 14

History of IaC

Slides (PDF)

Gotchas & Pitfalls

Bicep is forgiving, but a few things trip up almost everyone the first time. Read these before the assignment. Most gotchas point back to the chapter that teaches the safe pattern; use this page as a checklist when something misbehaves.

Deploying does not delete

Bicep deployments are additive: they create and update, but removing a resource from your template does not delete it from Azure on the next deploy. Newcomers expect their template to be the whole truth and are surprised when an old resource lingers. Bicep in practice covers teardown.

Fix: delete the resources you created with az resource delete against $CLASS_RG (or ask your teacher if your role blocks delete). Do not assume the template cleans up after itself. Do not run az group delete on the class resource group. For clean deletion of a whole template as one unit, see Deleting infrastructure.

Forgotten resources cost money

Every resource bills for as long as it exists, used or not. A storage account you created for a test and forgot is a small charge that runs forever.

Fix: tear down what you no longer need, the same day you finish with it. Make teardown part of the exercise, not a someday task. Bicep in practice walks the delete command.

Hardcoded secrets in the template

A password or key written as a literal in a .bicep file goes straight into git the moment you commit. That is a leaked secret.

Fix: use a @secure() parameter (never logged or stored in deployment history) or, better, a Key Vault reference so the value never touches the template at all. Same discipline as fetching your Postgres URL from Key Vault in Sequential Pipelines, applied to infrastructure. Details in Bicep in practice.

Passing @secure() on the command line

Marking a parameter @secure() stops it landing in deployment history. Typing the value as a plain az deployment ... --parameters dbAdminPassword=... argument can still leave it in your shell history.

Fix: for anything real, pass secrets via a parameters file that stays out of git, or a Key Vault reference. A dummy value for class practice is fine; never commit the file that held a real secret. Bicep in practice covers the @secure() pattern.

Storage account name clashes

Storage account names are globally unique across all of Azure, lowercase, 3 to 24 characters, letters and numbers only. Your first deploy often fails because someone, somewhere, already took the name. Azure Bicep warns about this on first deploy.

Fix: use a distinctive prefix (sthyf<yourname>), and treat a name clash as "pick another," not "my template is broken." Confirm you are deploying into $CLASS_RG (the name your teacher gave you), not inventing a new resource group. Also confirm az account show points at the shared HYF subscription before you deploy; the wrong subscription is a common "it works for the teacher but not for me" failure.

Module path typos

When main.bicep calls module storage 'modules/storage.bicep', that path is relative to the folder that contains main.bicep. A typo (module/storage.bicep, wrong filename, or the module file sitting next to main.bicep instead of under modules/) fails the deploy with a file-not-found style error that looks scarier than it is. Bicep in practice shows the expected layout.

Fix: keep modules/storage.bicep next to main.bicep as in the samples, and match the path string exactly (including the modules/ folder name).

LLM-invented Bicep properties

LLMs are good at drafting Bicep from a plain-English prompt, and also good at inventing property names or API versions that do not exist. The deploy then fails with a schema error that looks like "your Azure is broken."

Fix: check generated templates against the Bicep resource reference, and run az deployment group what-if before you apply. Never paste secrets into the chat. (⚠️ no real data, no PII)

Deploying without what-if first

Deploying a change straight to shared infrastructure means you find out what changed after it happened. On a resource group other people use, that is how accidents happen.

Fix: run az deployment group what-if before every deploy to shared resources, and read the diff (Bicep in practice). A change on a property you declared, that you did not make, means drift: someone edited the live resource by hand. Container noise (below) does not.

what-if never says "no change"

Once your template has a nested blob container, an unchanged template stops printing a clean result. Every run reports ~ on the container and the blob service, with - lines under them for properties like defaultEncryptionScope and deleteRetentionPolicy. Nothing is wrong: Azure fills in defaults on the live resource that your template never mentions, so what-if keeps reporting the gap. A - nested under a ~ is a property line, not a deletion; real deletions get their own top-level entry and their own count in the summary.

Fix: read the storage account line, not the resource count. It shows = when the account matches your template, and ~ with your change on it when you edited a tag or SKU. Do not treat "not a clean no-change" as a failure, and do not chase it. Reading a noisy preview shows the real output.

Your preview lists other people's resources

$CLASS_RG is shared with your whole cohort, so what-if also prints your classmates' storage accounts, each marked * (Ignore) with a matching to ignore count in the summary. This looks alarming the first time: it reads as though your deploy is about to touch someone else's work.

Fix: it is not. * means what-if found a live resource your template says nothing about, so it will leave it alone. Read only the line carrying your own sthyf… name and ignore the rest. The symbols are listed at the top of every what-if run, and Reading a noisy preview works through a real example.

Deployment history fills up

Azure keeps a limited deployment history per resource group (hard cap 800). Automatic cleanup usually prunes older records before you hit the ceiling; if a scope is already at the cap, the next deploy can fail until history is cleaned up.

Fix: this rarely bites students. Note that what-if and validate runs count toward the history too, not only real deploys, so a week of previewing adds up faster than you would guess. Do not rely on the portal Deployments blade as your archive (Azure Bicep shows where to read it). Your git history is the durable record of what your templates said.

The CI what-if fails on your hand-in PR

You added bicep.yml, it went green inside your fork, and then the same check turns red on the pull request you opened against the cohort repo. The log shows azure/login failing on empty credentials.

Fix: nothing is wrong with your workflow. GitHub never passes repository secrets to a pull request opened from a fork, so AZURE_CREDENTIALS is empty on the cohort repo no matter what you stored in your own Settings. Capture your green evidence from a pull request inside your own fork (base repository <you>/c55-data-week-14), as Deploy Bicep from CI/CD describes. A red Bicep check on the hand-in PR itself is expected and is not held against you.

The PR description check blocks your hand-in

A check named PR body check fails and your pull request is blocked, even though every file is correct. The error lists headings you have never seen.

Fix: the assignment repo ships .github/PULL_REQUEST_TEMPLATE.md, and the description must keep its headings (## What I built, ## How to review, ## How to run, ## What reviewers should see, ## Self-check). GitHub fills that template in only when you open the PR from the website or with gh pr create and no --body; open it through the API or with gh pr create --body "...", which is what most AI tools do, and the template is skipped silently. Click Edit on the description, paste the template, fill it in. Editing re-runs the check on its own, with no new commit.

Before you open the assignment PR

Walk this list once against your fork (not graded; it catches the mistakes above):

  1. You kept the starter shape: storage account in modules/storage.bicep, called from main.bicep, with the @secure() dummy parameter still there and no literal secrets committed.
  2. Task 1: main.bicep declares param environment, and that value reaches an Environment tag on the storage account.
  3. Task 2: both nested containers exist, raw and curated, under the same default blob service.
  4. You ran what-if against $CLASS_RG and saved the output; the useful capture is the tag change (~ tags.Environment: "dev" => "prod"), not a clean no-change.
  5. Deploy shows Succeeded; portal confirms the account and both containers.
  6. You tore down every resource you created (or asked your teacher), and recorded it as a line in docs/portal_confirm.md. Deleting the storage account is enough to remove its containers.
  7. WRITEUP.md and AI_ASSIST.md are your words, not an unedited paste.
  8. bash .hyf/test.sh passes the static checks (it cannot see your live Azure deploy).
  9. Your PR description keeps the five headings from .github/PULL_REQUEST_TEMPLATE.md, or the PR body check blocks the pull request.

Next up is not a chapter: if you have not submitted yet, use this list on the Assignment. For stacks, loops, and Bicep in CI, see Advanced Bicep.


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

CC BY-NC-SA 4.0 Icons

Built with ❤️ by the HackYourFuture community · Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.