Infrastructure As Code
Info
This lecture is based partly on Infrastructure as Code: Dynamic Systems for the Cloud Age by
Kief Morris.
1. What is Infrastructure As Code (IaC)?
The Iron Age vs. the Cloud Age
Technology changes in the Cloud Age
| Iron Age | Cloud Age |
|---|---|
| Physical hardware | Virtualized resources |
| Provisioning takes weeks | Provisioning takes minutes |
| Manual processes | Automated processes |
Ways of working in the Cloud Age
| Iron Age | Cloud Age | |
|---|---|---|
| Cost of change | high | low |
| Changes represent | failures (need to be managed/controlled) | learning and improvement |
| Optimization perspective | reduces opportunities to fail | maximize speed of improvement |
| Delivery | in batches | small changes |
| Testing | at the end | continuously |
| Release cycles | long | short |
| Architecture | monolithic (fewer, larger moving parts) | microservices (more smaller parts) |
| Configuration | physical or GUI-driven | Configuration-as-Code |
Defining IaC
Definition
"Infrastructure as Code means applying software engineering practices to the design and management of infrastructure." — Kief Morris
Benefits of IaC
- Enable rapid delivery of value (
changeto add value) - Reduce effort and risk of making
changesto infrastructure. - Enable users of infrastructure to customize (
change) based on their need. - Improve speed to troubleshoot and resolve failures (that likely come from
changes)
Success
- Use IaC to optimize for change: the ability to make changes both rapidly and reliably.
- The IaC Value Proposition
- Speed → faster provisioning
- Quality → fewer manual errors
- Reliability → consistent environments
- Transparency → code is documentation
Common Objections & Responses
Objection 1: We don’t make changes often enough
- Are systems built once and done?
- Are changes temporary?
- Is it a good thing to make it hard to change?
Stability comes from making changes.
???failure "Objection 2: We’ll automate later” - It is true that IaC has a steep curve, but ... - Automation enables faster delivery even for new things. - Automation makes it easier to write automated tests. - Automating an existing system is harder.
Objection 3: We must choose between speed and quality
- Organizations can't choose between being good at change or being good at stability.
- Either good at both or bad at both.
Four Key Metrics
- Based on DORA's Accelerate research (Google).
- Delivery lead time: time it takes to implement, test, and deliver changes to production system.
- Deployment Frequency: How often changes are deployed.
- Change Failure Rate: Percentage of changes that lead to impaired service (rollback or emergency patch)
- Mean Time to Recovery (MTTR)
Three Core Practices
- Define Everything as Code (scripts/config files/...)
- Continuously Test and Deliver All Work in Progress
- Build Small, Simple Pieces That You Can Change Independently
Key Takeaway
IaC transforms infrastructure from a cost center to a creative enabler.
2. Principles of Cloud Age Infrastructure
Systems Are Unreliable
- Assume your system runs on unreliable hardware
- Design for uninterrupted service when underlying resources change.
- Snowflake Systems
- Unique, hand-crafted servers
- Impossible to reproduce or rebuild:
The server that no one dares reboot - Drift, downtime, untested recovery paths
- Dependency fragility
Everything is Disposable
- Server as Pets vs. Server as Cattle
Minimize Variation
- Standardize stacks, avoid drift
- Golden images, module reuse
Ensure Repeatability
- Every process (build, deploy, rollback) should be repeatable by automation
How These Principles Interconnect
- Each principle supports automation, testability, and recovery
- Anti-Patterns to Watch
- Manual patches
- Untracked configuration changes
- Environment drift
Key Takeaway
Infrastructure principles mirror modern software engineering principles — reproducibility, modularity, disposability.
3. Hands-on: GitHub Actions Workflow for DockerHub
Repository preparation
- Create a GitHub repository with the following file/directory structure
.
├─ app/
│ ├─ package.json
│ └─ server.js
├─ Dockerfile
├─ .dockerignore
└─ .github/workflows/docker-publish.yml
app/server.js
app/package.json
Dockerfile
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS base
WORKDIR /usr/src/app
# Copy only what’s needed first to maximize cache
COPY app/package.json ./
# If you had deps, you'd run: npm ci --only=production
# For this demo with no deps, just proceed
COPY app/ ./
EXPOSE 8080
CMD ["node", "server.js"]
One-file GitHub Actions Workflow
What it does
- Builds on PRs (no push to Hub).
- Pushes to Docker Hub on push to default branch and on Git tags (e.g., v1.2.3).
- Generates smart tags (latest, branch name, git SHA, and release version).
- Uses Buildx cache for speed.
- Optional multi-arch (commented line—enable when desired).
Initial setup
- On your Docker Hub account:
- Create a Docker Hub Access Token (PAT) (Account Settings → Security).
- Scope: “Read, Write” for repos (keep minimal).
- In your GitHub repo → Settings → Secrets and variables → Actions → New repository secret:
DOCKERHUB_USERNAME= your Docker Hub usernameDOCKERHUB_TOKEN= the PAT you created
- In GitHub repo → Settings → Actions → General:
- Workflow permissions: Read and write (needed for cache).
- Allow GitHub Actions to create and approve pull requests from workflows: off (not needed).
Add .github/workflows/docker-publish.yml
name: Build and Publish Docker Image
on:
push:
branches: [ "main" ] # change if your default branch is different
tags: [ "v*" ] # pushes version tags like v1.2.3
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login only when we intend to push (not on PRs)
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract Docker metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/ci-demo
tags: |
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
type=sha
labels: |
org.opencontainers.image.title=ci-demo
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Build (PR) or Build+Push (main/tags)
uses: docker/build-push-action@v6
with:
context: .
# platforms: linux/amd64,linux/arm64 # ← enable for multi-arch
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- Push the above yaml file.
- Open
GitHub→Actions tab→ watch the run. - On success, check Docker Hub: image should appear with latest and main tags.