SOP: Platform Terraform Changes
SOP: Platform Terraform Changes
Purpose
Document the standard workflow for making infrastructure changes via Terraform in the
pal-e-platform and pal-e-services repos. All changes go through CI — no manual tofu apply allowed (except break-glass).Prerequisites
- Access to Forgejo (
forgejo_admin/pal-e-platform,ldraney/pal-e-services) - Woodpecker CI activated on both repos
- Branch protection enabled on
main— direct push blocked
Standard Workflow
pal-e-platform (CI-driven apply, individual TF_VAR_* secrets)
- Create Forgejo issue from plan phase or bug note
- Create branch from issue (naming:
{issue-num}-{slug}) - Make changes in worktree or local branch
- Local validation:
cd terraform && tofu init && tofu fmt -check && tofu validate - Local plan (optional):
tofu plan -lock=false. The-lock=falseflag is REQUIRED when running from a worktree or any non-CI context. - Push and create PR — body MUST include
Closes #N - CI runs automatically: validate + plan on PR, plan output posted as PR comment
- Review plan comment on the PR — verify expected changes
- Merge PR — triggers apply step on main
- Verify — check Woodpecker pipeline success, confirm resources in cluster
pal-e-services (CI-driven apply, bundled tfvars_content secret)
k3s.tfvars is gitignored (contains sensitive config). Instead of individual TF_VAR_* secrets, all tfvars are bundled into a single base64-encoded tfvars_content Woodpecker secret. CI decodes it at runtime.- Create Forgejo issue from plan phase or bug note
- Create branch from issue (naming:
fix/{issue-num}-{slug}) - Make terraform changes in worktree (
services.tf,variables.tf, etc.) - Edit
~/secrets/pal-e-services/k3s.tfvarswith new variable values - Update tfvars_content secret:
base64 -w0 ~/secrets/pal-e-services/k3s.tfvars mcp__woodpecker__update_repo_secret(repo_full_name="ldraney/pal-e-services", name="tfvars_content", value=<base64 output>) - Local plan (optional): Symlink k3s.tfvars into clone, run
tofu plan -lock=false -var-file=k3s.tfvars - Push and create PR — body MUST include
Closes #N - CI runs automatically: validate + plan on PR, plan output posted as PR comment
- Review plan comment on the PR — verify expected changes
- Merge PR — triggers apply step on main
- Verify — check resources in cluster (
kubectl get secret,kubectl get ns, ArgoCD UI)
Critical: Step 5 (updating
tfvars_content) must happen before pushing the branch, otherwise CI plan will fail because it won't have the new variables. See sop-secrets-management for the full procedure.pal-e-deployments (CI validation on PR)
Kustomize overlays are validated in CI before merge:
- Push PR — Woodpecker CI triggers automatically
- CI validates:
kubectl kustomizeon all changed overlays +kubectl apply --dry-run=serverfor server-side schema validation - QA reviews — code review + CI green
- Merge — ArgoCD picks up the overlay on next sync
What NOT to Do
- No manual
tofu apply— all applies go through CI on merge to main (except break-glass) - No direct push to main — branch protection is enforced
- No
--forcepush — creates state drift risk - No skipping plan review — always verify the plan comment before merging
- No
tofu planwithout-lock=falsefrom worktrees — a locked plan from a worktree blocks CI apply on ALL branches - No forgetting to update
tfvars_contentwhen editingk3s.tfvarsfor pal-e-services — CI will use stale values
Break-Glass Procedure
If CI is broken and an emergency change is needed:
- SSH to node
cd ~/pal-e-platform/terraform(or~/pal-e-services/terraform)- For pal-e-platform:
make tofu-secrets(renders Salt pillar secrets tosecrets.auto.tfvars) - For pal-e-services: symlink
~/secrets/pal-e-services/k3s.tfvarsinto terraform dir tofu init && tofu plan(review carefully)tofu apply(only after plan review)- File an issue immediately to fix CI
- Push the change through a PR retroactively
CI Pipeline Details
pal-e-platform
- Pipeline file:
.woodpecker.yaml - Secrets: 17 Woodpecker repo secrets (
TF_VAR_*pattern) - Image:
ghcr.io/opentofu/opentofu:1.9(all steps) - Events: validate + plan on
pull_request, apply onpushtomain
pal-e-services
- Pipeline file:
.woodpecker/terraform.yaml - Secrets: 3 Woodpecker repo secrets (
kubeconfig_content,forgejo_token,tfvars_content) - Image:
ghcr.io/opentofu/opentofu:1.9(all steps) - Events: validate + plan on
pull_request, apply onpushtomain - Secret delivery:
tfvars_contentis base64-decoded to/tmp/k3s.tfvarsat runtime, then passed via-var-file
DORA Baseline (2026-03-14)
- Deployment Frequency: ~1 deploy/day (repo just activated, single active contributor)
- Lead Time for Changes: Not yet measurable (Woodpecker API doesn't expose timestamps)
- Change Failure Rate: 0% on main applies (1/1 successful), 37.5% on PR pipelines (expected — initial CI setup iteration)
- MTTR: No production failures yet to measure
- Note: These are bootstrap numbers from day 1. Re-measure after 2 weeks of steady-state operation.
Related
plan-pal-e-platform— Platform Hardening plan.woodpecker.yamlin pal-e-platform — CI pipeline definition.woodpecker/terraform.yamlin pal-e-services — CI pipeline definitionsop-secrets-management— how secrets reach CI (including tfvars_content update procedure)deployment-lessons— operational lessons learned