Quick note: The quick test is available to everyone for free. Only logged‑in learners get progress saved automatically.
Why this matters
As a Platform Engineer, you enable teams to ship fast and safely. Reusable IaC modules and standards give you:
- Speed: teams compose proven building blocks instead of reinventing infrastructure.
- Safety: guardrails built into modules reduce outages and misconfigurations.
- Consistency: naming, tagging, and structure make ops and auditing easier.
- Scalability: updates roll out via versions, not copy-paste changes.
Real tasks you will face:
- Create a Terraform VPC module used across dozens of services.
- Enforce organization-wide tagging standards (cost, owner, env).
- Version and publish modules; handle breaking changes responsibly.
- Test modules in CI and apply policy checks before deployment.
Who this is for
- Platform/DevOps/Cloud engineers who maintain shared infrastructure.
- Backend engineers contributing to IaC repositories.
- Teams moving from hand-written stacks to standardized modules.
Prerequisites
- Basic IaC knowledge (e.g., Terraform, Ansible, or Helm basics).
- Familiarity with Git, branching, and pull requests.
- Comfort with cloud provider basics (networking, IAM, compute).
Concept explained simply
A reusable module is a small, well-defined package of infrastructure logic with clear inputs, outputs, and documentation. Standards are the rules that keep all modules predictable: naming, tagging, versioning, structure, tests, and policies.
Mental model
- Think of modules like Lego bricks: snap together, minimal surprises, versioned, and documented.
- Standards are the studs and sizes that make bricks compatible across sets.
Core standards for reusable IaC
- Structure: consistent files and folders (e.g., variables/outputs/examples/README).
- Naming: predictable resource names and input variable names.
- Tagging/labels: required tags for owner, env, cost center, compliance.
- Versioning: semantic versioning (MAJOR.MINOR.PATCH).
- Inputs/Outputs: validated inputs; stable output names.
- Documentation: purpose, inputs/outputs table, examples, version notes.
- Testing: lint/validate/plan checks; optional integration tests.
- Security: least privilege, provider/version pinning, no secrets in code.
- Policy guardrails: pre-commit hooks and policy-as-code checks.
- Publishing: clear release process with changelog and tags.
Quick checklist: Is your module ready?
- Has README with purpose and usage example.
- Has variables with type, descriptions, sensible defaults, and validation.
- Outputs named and described.
- Version pinned for providers/modules.
- Tagging/labels enforced and tested.
- Lint/validate passes in CI.
- Semver tag created and changelog updated.
Worked examples
Example 1 — Terraform VPC module skeleton
Goal: a minimal, reusable VPC module with inputs, outputs, validation, and tagging.
modules/
vpc/
main.tf
variables.tf
outputs.tf
versions.tf
README.md
examples/
simple/
main.tf
Key files (abridged)
// versions.tf
terraform {
required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
// variables.tf
variable "name" {
type = string
description = "Base name for resources"
}
variable "cidr_block" {
type = string
description = "VPC CIDR"
validation {
condition = can(cidrnetmask(var.cidr_block))
error_message = "cidr_block must be a valid CIDR."
}
}
variable "tags" {
type = map(string)
description = "Additional tags"
default = {}
}
locals {
required_tags = {
Owner = coalesce(try(var.tags["Owner"], null), "unset")
Env = coalesce(try(var.tags["Env"], null), "dev")
CostCenter= coalesce(try(var.tags["CostCenter"], null), "0000")
}
all_tags = merge(var.tags, local.required_tags)
}
// main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = merge({
Name = "${var.name}-vpc"
}, local.all_tags)
}
// outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
description = "VPC ID"
}
Example usage:
// examples/simple/main.tf
module "vpc" {
source = "../../" // or registry path
name = "orders"
cidr_block = "10.20.0.0/16"
tags = {
Owner = "platform"
Env = "prod"
CostCenter = "1234"
}
}
Release with a semver tag (e.g., v1.0.0). Breaking changes require a new major version.
Example 2 — Helm chart standardization
Goal: a Helm chart that enforces common labels and validates values.
charts/
webapp/
Chart.yaml
values.yaml
values.schema.json
templates/
_labels.tpl
deployment.yaml
Key snippets (abridged)
# templates/_labels.tpl
{{- define "common.labels" -}}
app.kubernetes.io/name: {{ include "webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: Helm
owner: {{ .Values.labels.owner | quote }}
env: {{ .Values.labels.env | quote }}
cost-center: {{ .Values.labels.costCenter | quote }}
{{- end -}}
# templates/deployment.yaml
metadata:
labels:
{{ include "common.labels" . | indent 4 }}
# values.schema.json (partial)
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"labels": {
"type": "object",
"required": ["owner", "env", "costCenter"],
"properties": {
"owner": {"type": "string", "minLength": 1},
"env": {"type": "string", "enum": ["dev", "staging", "prod"]},
"costCenter": {"type": "string", "pattern": "^\\d{4}$"}
}
}
},
"required": ["labels"]
}
This enforces organization-wide labeling and prevents invalid values at install time.
Example 3 — Ansible role with defaults and idempotency
Goal: a role that manages users consistently across hosts.
roles/
users/
defaults/main.yml
tasks/main.yml
README.md
Key snippets (abridged)
# defaults/main.yml
users_list: []
# tasks/main.yml
- name: Ensure users present
ansible.builtin.user:
name: "{{ item.name }}"
state: present
shell: "/bin/bash"
loop: "{{ users_list }}"
loop_control:
label: "{{ item.name }}"
tags: ["users"]
Usage example:
- hosts: all
roles:
- role: users
vars:
users_list:
- { name: "appsvc" }
- { name: "ops" }
Document inputs (users_list) and expected behavior in README. Tag tasks for selective runs.
Exercises
Do these now. They mirror the graded exercises below and prepare you for the quick test.
Exercise 1 (ex1): Terraform module skeleton
Create a reusable Terraform module for an S3 bucket that enforces tagging and versioning.
- Files: versions.tf, variables.tf, main.tf, outputs.tf, README.md, examples/simple/main.tf
- Required inputs: name (string), force_destroy (bool, default false)
- Required tags: Owner, Env, CostCenter; fail if missing
- Enable bucket versioning by default
- Output: bucket_id, bucket_arn
Exercise 2 (ex2): Tagging standard + policy
Define your organization tagging standard and a policy check.
- Write a short JSON/YAML policy that requires tags Owner, Env, CostCenter.
- Show how your Terraform module satisfies the policy (example usage).
- Describe how this would run in CI (lint, validate, policy check).
Self-checklist
- Module has clear README and example usage.
- Inputs validated; sensible defaults exist.
- Outputs named and described.
- Tags enforced; policy captures the rule.
- Version pinning present for providers/modules.
Common mistakes and how to self-check
- Skipping validation: Inputs accept anything. Fix: add type and validation blocks.
- Hardcoding names: Breaks reuse. Fix: derive names from inputs.
- Hidden breaking changes: You change an output name silently. Fix: bump MAJOR and document in changelog.
- No examples: Users guess how to use it. Fix: include an examples/ folder.
- Untagged resources: Costs become opaque. Fix: enforce tags via locals or templates.
- Unpinned versions: Builds change unexpectedly. Fix: pin providers/modules with version constraints.
Practical projects
- Build a network foundation pack: Terraform modules for VPC, subnets, NAT, security groups; publish v1.0.0.
- Create a Helm base chart with required labels and a values.schema.json; derive two service charts from it.
- Write an Ansible role library (users, ntp, logging) with defaults and tags; test with a local inventory.
Mini tasks to level up your projects
- Add a CHANGELOG.md and document every release.
- Introduce a deprecation notice: warn in README before removing inputs in next major.
- Set up pre-commit hooks for formatting and linting.
Learning path
- Standardize: Decide naming, tagging, and versioning rules for your org.
- Skeletons: Create module templates (Terraform/Helm/Ansible) with placeholders.
- First releases: Publish v1.0.0 of two core modules; add examples.
- Guardrails: Add CI steps for lint, validate/plan, and policy checks.
- Scale: Onboard two product teams; gather feedback; iterate with minor versions.
Next steps
- Refactor an existing hand-written stack to use your modules.
- Add integration tests for one module using a sandbox environment.
- Document a rollback plan for a failed module upgrade.
Mini challenge
You maintain a widely used Terraform RDS module. You must add storage autoscaling (new input) without breaking users. What do you change, how do you version it, and how do you communicate the update? Write 5–8 bullet points covering design, defaults, validation, version bump, docs, and rollout plan.
Ready to check your understanding? Open the Quick Test below. Tip: if you log in, your test result is saved to your progress.