Chapter 00: Your Lab Environment
Every hands-on chapter from 02 onwards runs against one local cluster that you build here once. Nothing in the core track needs a cloud account, a domain, or an API key: the cluster runs in Docker with kind, and Flux reconciles the local profile of the SafeOps platform - the production GitOps tree minus the parts that only make sense with a cloud provider.
Read Chapter 01 for the ideas; do this chapter when you are ready to type.
Learning Objectives
By the end of this chapter, you will be able to:
- Provision the SafeOps kind cluster with Terraform and prove that Flux is green
- Explain which platform components the local profile leaves out, and why
- Generate your own age key and register it for the secrets you will encrypt in Chapter 04
- Run a test pod in an environment namespace that enforces Pod Security
restricted
1. Install the Tools
| Tool | Why | Check |
|---|---|---|
| Docker (OrbStack, Docker Desktop, Colima) with ~6 CPU / 8 GB | the cluster nodes are containers | docker info |
| Terraform ≥ 1.5 | provisions the cluster and the Flux bootstrap | terraform version |
| kind ≥ 0.30 | local Kubernetes | kind version |
| kubectl, flux CLI | you will use both constantly | kubectl version --client, flux version --client |
| age, sops | Chapter 04 | age-keygen --version, sops --version |
| pre-commit, make | Chapters 02 and 05 | pre-commit --version |
2. Get the Platform Repository
The course is taught from the SafeOps reference implementation. Fork it, then clone your fork - from Chapter 03 on you will commit to it and Flux will deploy from it.
git clone https://github.com/<you>/sre.git
cd sre
The upstream repository is public; a fork is public by default too, and Flux reads it anonymously. Only if you make your fork private does Flux need a token: create a GitHub personal access token with
reporead scope and pass it asTF_VAR_flux_git_token.
3. Build the Cluster
cd infra/terraform/kind_cluster
export TF_VAR_flux_git_repository_url="https://github.com/<you>/sre.git" # your fork
# export TF_VAR_flux_git_token="<PAT with repo read>" # only for a private fork
terraform init
terraform plan -out=tfplan # read it: 2 nodes, Traefik, metrics-server, Flux, generated secrets
terraform apply tfplan
Two to three minutes later the cluster exists and Flux starts pulling images. Terraform also:
- merges the kubeconfig into
~/.kube/configas contextsre-control-plane - generates the runtime secrets the applications need (
backend-secrets,app-postgres-app, MinIO credentials) - never committed, unique to your cluster - generates
age.agekeyin the repository root (git-ignored) and stores it in the cluster assops-age
4. Register Your Key for SOPS
cd ../../.. # back to the repository root
scripts/sops-setup.sh --local
git add .sops.yaml && git commit -m "sops: register my local key" && git push
This writes your public key into the flux/secrets/local/ rule of .sops.yaml. Only files under that path are encrypted for you; the platform’s own secrets stay encrypted for the platform key, which you do not have and do not need.
5. Prove It Is Green
Wait for the images to arrive (five minutes on a normal connection), then:
kubectl config use-context sre-control-plane
flux get kustomizations -A # every row READY=True
kubectl get pods -A --no-headers | grep -vE 'Running|Completed' # prints nothing
make smoke-test # 9/9
These three commands are your baseline check. Every later chapter starts by running them; if they are not clean, fix the environment before you start the exercise, not after.
6. What the Local Profile Contains
| On your kind cluster | Cloud track only (Hetzner profile) |
|---|---|
namespaces develop, staging, production, lab with NetworkPolicies, quotas, Pod Security | cert-manager, external-dns, Cloudflare DNS/TLS (appendix) |
backend + frontend in all three environments, Traefik ingress on localhost:8080 | Dex, Headlamp, OIDC RBAC |
Prometheus + Grafana (kubectl -n observability port-forward svc/kube-prometheus-stack-grafana 3000:80) | Uptrace tracing export |
| CloudNativePG with one Postgres cluster per environment, backups to MinIO | k8s-ai-monitor (needs an LLM key - Chapter 13) |
| Kyverno with both policy packs in Audit, Flagger canaries, Chaos Monkey | Flux image automation (writes back to Git) |
When a chapter says “Cloud track”, that step needs something from the right-hand column. Read it, understand it, and move on - the core exercise works on the left-hand column alone.
7. Running Test Pods
The environment namespaces enforce Pod Security restricted. A plain kubectl run busybox is rejected before it starts - that is a guardrail, not a bug. The course uses a wrapper that adds the required security context:
scripts/lab-pod.sh -n develop -i curlimages/curl -l app=frontend -- curl -sf http://backend/healthz
scripts/lab-pod.sh -n develop --daemon np-debug # long-lived; then kubectl -n develop exec np-debug -- ...
The lab namespace is the exception: Pod Security only warns there, so drills that must observe a violation (Chapter 15) can run.
8. Tear Down and Rebuild
cd infra/terraform/kind_cluster && make destroy # cluster and state gone
terraform apply # ...and back, same fork, same key
Rebuilding is cheap. When a chapter leaves your cluster in a state you do not understand, rebuild rather than debug the debugging.
Knowledge Check
Complete the Quiz before Chapter 01 - it checks that you can tell your own responsibilities (fork, key, token) from what the automation does for you.