Post

IaC - Part 1

IaC - Part 1

Building Cloud Foundations with Infrastructure‑as‑Code: A Practical Introduction Using Azure Bicep

Part 1: Why IaC Matters, What I’m Building, and How Bicep Fits In

Cloud engineering has matured to the point where manual configuration is no longer just inefficient — it’s a liability. Modern teams need repeatability, auditability, and the ability to deploy infrastructure with the same rigor they apply to application code. That’s where Infrastructure‑as‑Code (IaC) comes in.

Over the past few months, I’ve been refining a modular Azure environment using Bicep, Microsoft’s domain‑specific language (DSL) for declarative cloud deployments. The goal wasn’t just to spin up resources — it was to build a clean, scalable, and reusable foundation that mirrors how a real enterprise environment operates.

This post is the first in a two‑part series. Here, I’ll introduce the concepts, the project, and the role Bicep plays in Azure. In Part 2, I’ll walk through the exact steps to build and deploy the environment yourself.


What Is Infrastructure‑as‑Code (IaC)?

Infrastructure‑as‑Code is the practice of defining cloud resources using code instead of clicking through a portal. Instead of manually creating virtual networks, storage accounts, or key vaults, you describe them in files that can be version‑controlled, reviewed, tested, and deployed automatically.

IaC provides:

  • Consistency — Deploy the same code twice, get the same infrastructure twice.
  • Repeatability — Create dev, test, and prod environments with the same templates.
  • Auditability — Every change is tracked in Git.
  • Automation — Integrates with CI/CD pipelines for fully automated provisioning.
  • Scalability — Code grows with your environment in a modular, maintainable way.

Azure supports several IaC tools (Terraform, Pulumi, Azure Resource Manager-ARM templates), but Bicep has become the native, first‑class option for teams who want tight integration with Azure’s resource model.


Why Bicep?

Bicep is Microsoft’s answer to the complexity of ARM templates. It’s a declarative language that compiles down to ARM JSON, but with a syntax that’s dramatically easier to read and maintain.

Key advantages of Bicep:

  • Cleaner syntax — No more 300‑line JSON files.
  • Native Azure support — Every resource type is available on day one.
  • Modular structure — Break your environment into reusable components.
  • Type safety and IntelliSense — VS Code guides you as you write.
  • No state file — Azure itself is the source of truth.
  • Easy to learn — Especially for engineers familiar with ARM or Terraform. If I can learn it, so can you!

In short, Bicep gives you the power of ARM with the readability of a modern DSL.


The Project: Building a Modular Azure Environment

The environment I built is designed to reflect real‑world enterprise patterns: modular, parameterized, and scalable. Instead of a single monolithic template, the project is broken into logical components — each responsible for a specific part of the infrastructure.

Core modules include:

  • Resource Group provisioning
  • Virtual Network and subnets
  • Network Security Groups (NSGs)
  • Public IPs and NICs
  • Key Vault
  • Storage Accounts
  • Virtual Machines
  • Role Assignments and Access Policies

Each module is self‑contained, reusable, and version‑controlled. The root main.bicep file orchestrates the deployment by calling these modules with the appropriate parameters.

This structure mirrors how large organizations manage IaC: small, focused modules that can be combined into larger architectures.


How Bicep Fits into Azure Deployments

When you deploy Bicep, Azure performs a few key steps behind the scenes:

  1. Bicep compiles to ARM JSON
    You never see the JSON unless you want to — but Azure uses it internally.

  2. Azure Resource Manager (ARM) interprets the template
    ARM determines what needs to be created, updated, or left alone.

  3. ARM performs an idempotent deployment
    • If a resource already exists and matches the template, ARM leaves it untouched.
    • If it needs to be updated, ARM applies the change.
    • If it’s missing, ARM creates it.
  4. Azure logs the entire deployment
    You get full visibility into what changed, when, and why.

This workflow is the backbone of Azure’s IaC model.


Designing for Reusability and Scale

One of the most important lessons in IaC is that structure matters. A flat repo with a single template quickly becomes unmanageable. Instead, I built a structure that supports:

  • Multiple environments (dev, test, prod)
  • Parameterization for environment‑specific values
  • Module reuse across projects
  • Clear separation of concerns
  • Easy onboarding for new engineers

A typical layout looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
iac-repo/
├── main.bicep
├── modules/                     # Reusable building blocks (no env-specific values)
│   ├── compute/
│   │   ├── vm/
│   │   │   ├── vm.bicep
│   │   │   ├── variables.bicep
│   │   │   └── outputs.bicep
│   │   └── vmss/
│   ├── networking/
│   │   ├── vnet/
│   │   ├── subnet/
│   │   └── nsg/
│   ├── data/
│   │   ├── storage-account/
│   │   └── sql/
│   └── messaging/
│       └── service-bus/
│
├── environments/                # Environment-specific configs
│   ├── dev/
│   │   ├── dev.bicep
│   │   └── dev.bicepparam
│   ├── test/
│   │   ├── test.bicep
│   │   └── test.bicepparam
│   └── prod/
│       ├── prod.bicep
│       └── prod.bicepparam
│
├── pipelines/                   # CI/CD automation
│   ├── github-actions/
│   │   └── deploy.yml
│   ├── azure-devops/
│   │   └── pipeline.yaml
│   └── templates/
│       └── validate-plan.yml
│
├── policies/                    # Policy-as-code (OPA, Azure Policy, Sentinel)
│   └── azure-policy/
│
├── docs/                        # Architecture diagrams, runbooks, ADRs
│   ├── architecture.md
│   └── decisions/
│
└── tools/                       # Helper scripts (linting, validation, wrappers)
    ├── validate.sh
    ├── format.sh
    └── pre-commit-hooks/
This post is licensed under CC BY 4.0 by the author.