Infrastructure As Code

Details

This lecture is based partly on Infrastructure as Code: Dynamic Systems for the Cloud Age by Kief Morris.

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
Details
Common Objections & Responses
Objection 1: We don’t make changes often enough

Stability comes from making changes.

Objection 2: We’ll automate later”
Objection 3: We must choose between speed and quality
Four Key Metrics
Three Core Practices
Key Takeaway

IaC transforms infrastructure from a cost center to a creative enabler.

Principles of Cloud Age Infrastructure

Systems Are Unreliable
Everything is Disposable
Minimize Variation
Ensure Repeatability
How These Principles Interconnect
Key Takeaway

Infrastructure principles mirror modern software engineering principles — reproducibility, modularity, disposability.

3. Hands-on: GitHub Actions Workflow for DockerHub

Repository preparation

```pgsql . ├─ app/ │ ├─ package.json │ └─ server.js ├─ Dockerfile ├─ .dockerignore └─ .github/workflows/docker-publish.yml

app/server.js
1
2
3
4
5
6
const http = require('http');
const port = process.env.PORT || 8080;
const server = http.createServer((req, res) => {
  res.end('Hello from CI-built container!\n');
});
server.listen(port, () => console.log(`Listening on ${port}`));
app/package.json
1
2
3
4
5
6
7
8
{
  "name": "ci-demo",
  "version": "1.0.0",
  "main": "server.js",
  "license": "MIT",
  "scripts": { "start": "node server.js" },
  "dependencies": { }
}
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
# 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"]
.dockerignore
1
2
3
4
5
6
.git
.github
node_modules
*.log
Dockerfile
README.md
Sanity check
1
2
3
docker build -t local/ci-demo:dev .
docker run -p 8080:8080 local/ci-demo:dev
curl -s localhost:8080
One-file GitHub Actions Workflow
What it does
Initial setup
Add .github/workflows/docker-publish.yml
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
50
51
52
53
54
55
56
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: $
          password: $

      - name: Extract Docker metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: $/ci-demo
          tags: |
            type=ref,event=branch
            type=ref,event=tag
            type=raw,value=latest,enable=
            type=sha
          labels: |
            org.opencontainers.image.title=ci-demo
            org.opencontainers.image.source=$/$
            org.opencontainers.image.revision=$

      - 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: $
          tags: $
          labels: $
          cache-from: type=gha
          cache-to: type=gha,mode=max
Release a version
1
2
git tag v1.0.0
git push origin v1.0.0