Infrastructure as Code, GitOps & Secrets¶
What this lesson gives you
If clicking in a console deploys your infrastructure, you can’t reproduce, review, or audit it. IaC, GitOps, and disciplined secrets management make infrastructure itself a versioned, governed artifact.
Estimated time: 22 min read · Part: CI/CD Across Cloud, On-Prem & Hybrid
Why Infrastructure Must Be Code¶
The moment you click “Create VPC” or “Launch instance” in a cloud console and don’t record it, you have created debt. You cannot reproduce that environment: the next time you need it (disaster recovery, a new region, a staging environment for testing), you have to re-click through the console and hope nothing has changed since. You cannot review it: there is no diff, no PR, no audit trail. You cannot audit it: you can’t answer “who changed the firewall rule on Tuesday and why.”
Infrastructure as Code (IaC) is the practice of expressing infrastructure configuration in text files that are stored in version control. IaC is to infrastructure what version control is to application code: it gives you reproducibility, diffability, and auditability as first-class properties.
IaC Tools: The Landscape¶
Terraform / OpenTofu (HCL): the industry-standard IaC tool, used by the majority of platform engineering teams. Terraform uses a declarative language (HCL, HashiCorp Configuration Language) to describe the desired state of infrastructure. terraform plan shows a diff between what Terraform knows (state file) and what the code declares. terraform apply makes the cloud match the declaration. Terraform supports hundreds of providers (AWS, GCP, Azure, Kubernetes, Datadog, GitHub, PagerDuty — nearly everything with an API). OpenTofu is the community fork created after HashiCorp changed Terraform’s license; it is API-compatible and increasingly the choice for teams that require an open-source license.
Pulumi (Python / TypeScript / Go / C#): IaC in real programming languages. Instead of HCL, you write Python classes and functions. This is a significant advantage for teams that are already strong Python engineers: your infrastructure code is typed, testable with pytest, importable as a library, and refactorable with standard IDE tooling. pulumi preview shows a diff; pulumi up applies it. Pulumi supports the same cloud providers as Terraform (many via auto-generated providers from Terraform provider schemas). The tradeoff: steeper learning curve for people who are not engineers, and a smaller ecosystem of community examples.
AWS CDK (Python or TypeScript): the AWS Cloud Development Kit lets you write infrastructure in Python (or TypeScript) and synthesize it to CloudFormation JSON/YAML. AWS-only, but extremely expressive for AWS resources. L2 constructs (higher-level abstractions) let you create a fully configured ECS service with load balancer and security groups in a few lines of code. The generated CloudFormation is managed by the CDK CLI. Well-suited to AWS-centric shops where the team already writes Python.
Bicep: Microsoft’s domain-specific language (DSL) for Azure Resource Manager templates — a significant improvement over writing raw ARM JSON. Azure-only. The right choice when deploying Azure-native services and the team is in the Microsoft ecosystem.
Terraform State Management¶
Terraform tracks what it has created in a state file (terraform.tfstate). By default this is local, which is dangerous for team use: different engineers might have different state files, leading to conflicting applies that create duplicate or orphaned resources. Remote state backends solve this:
Never Edit State Manually
The Terraform state file is a JSON document that maps declared resources to real cloud resource IDs. Editing it manually is extremely dangerous: a wrong edit can cause Terraform to believe a resource doesn’t exist and recreate it (destroying the old one), or believe it does exist when it doesn’t (leaving phantom references). State manipulation must be done via terraform state mv, terraform state rm, or terraform import — the CLI commands that maintain internal consistency. The state file should also never be committed to Git; it contains sensitive information (resource IDs, sometimes attribute values) and is not meant to be version-controlled directly.
Pulumi in Python: Typed Infrastructure¶
GitOps: Git as the Source of Truth¶
GitOps is a deployment model where the Git repository is the single source of truth for the desired state of the system. An in-cluster agent continuously reconciles the live state of the cluster against the declared state in Git. The consequence is a powerful inversion: a deployment is no longer “run a pipeline that pushes to the cluster” — it is “merge a PR, and the cluster converges to match.”
The Building Manager Analogy
GitOps is like a building manager who continuously walks the halls making sure every room matches the floor plan. If a tenant moves a wall without updating the architectural drawings — that’s drift, and it happens in Kubernetes too when someone runs kubectl edit directly on a production resource. The building manager (ArgoCD / Flux) notices the discrepancy and moves the wall back. The only way to change the building legitimately is to update the floor plan (the Git repository). Every change is reviewed, approved via PR, and recorded in commit history.
Argo CD is the most widely deployed GitOps tool. It is a Kubernetes controller that watches one or more Git repositories for changes to Kubernetes manifests, Helm charts, or Kustomize overlays. When it detects a diff between Git and the cluster, it applies the Git state. It supports multi-cluster deployments via ApplicationSet, SSO integration, and a web UI that shows the sync status and resource health of every Application.
Flux CD uses a modular approach: the Source Controller watches Git; the Kustomize Controller applies Kustomize overlays; the Helm Controller manages Helm releases; the Image Automation Controller can update the image tag in Git automatically when a new image is pushed. Flux is fully GitOps Toolkit-based: all configuration is expressed as Kubernetes CRDs, which means Flux can manage itself via GitOps.
ArgoCD Application Manifest¶
Rollback in GitOps
In a GitOps workflow, rollback is a git revert or a PR that reverts the offending commit. Argo CD detects that the target revision has changed and reconciles the cluster back to the previous state. This is operationally cleaner than “kubectl rollout undo” because the Git history records why the rollback happened and who approved it — the audit trail is complete. The cluster state is always traceable to a specific commit SHA.
Secrets Management: Never Commit Plaintext¶
The most expensive mistake in platform engineering is committing a secret to a Git repository. Even if caught and deleted immediately, the secret is in the git history and must be treated as compromised. Rotation is mandatory and urgent. If the repository is public, automated bots scan GitHub continuously for credential patterns (AWS access keys, GCP service account keys, Stripe API keys) and will find the secret within minutes of the push.
Real-World Incident: The Committed Password
A two-person startup was moving fast. Their Django application’s production settings file, including a hardcoded database password, was accidentally committed to a public GitHub repository. They noticed eight hours later during a code review. In that eight-hour window, automated credential-scraping bots — which scan GitHub’s public event stream in real time — had already found the password, logged into the database, and exfiltrated the user table including email addresses and bcrypt password hashes. The startup had to notify all users, rotate every credential in the system, engage an incident response firm, and file regulatory notifications. The total cost exceeded $200,000.
After the incident, they implemented: Vault for all secrets, SOPS for secrets that needed to live near the code, gitleaks as a pre-commit hook and CI gate to catch any secret-shaped string before it could be pushed.
Secrets Management Options¶
| Tool | Open Source? | Cloud-Agnostic? | Auto-Rotation | K8s Integration | Best For |
|---|---|---|---|---|---|
| HashiCorp Vault | Yes (BSL) / Ent. | Yes | Yes (dynamic secrets) | Vault Agent, ESO | Multi-cloud, complex secret workflows, dynamic DB credentials |
| AWS Secrets Manager | No (managed) | AWS-only | Yes (built-in Lambda rotation) | Via ESO | AWS-native apps; RDS, DocumentDB automatic rotation |
| GCP Secret Manager | No (managed) | GCP-only | Partial (version-based) | Via ESO | GCP-native apps; Workload Identity-based access |
| Azure Key Vault | No (managed) | Azure-only | Yes (cert/key rotation) | Via ESO / CSI driver | Azure-native apps; Entra ID integration |
| External Secrets Operator | Yes (Apache 2.0) | Yes (any backend) | Syncs on schedule | Native K8s CRD | Pulling secrets from any backend into K8s Secrets automatically |
| SOPS (Secrets OPerationS) | Yes (Mozilla) | Yes | No | Via FluxCD / manual | Encrypting secrets in Git repos; GitOps-friendly |
| Sealed Secrets | Yes (Apache 2.0) | Yes | No (re-seal on rotation) | Native K8s controller | Sealing K8s Secrets with cluster public key; simple GitOps |
External Secrets Operator¶
The External Secrets Operator (ESO) runs as a controller in the cluster. It watches ExternalSecret CRD objects, fetches the referenced secret values from an external backend (AWS Secrets Manager, GCP Secret Manager, Vault, etc.), and creates or updates a standard Kubernetes Secret object. The application reads the K8s Secret via envFrom or a volume mount — it never calls the secrets backend directly and doesn’t need credentials to do so.
SOPS: Encrypted Secrets in Git¶
SOPS (Secrets OPerationS, by Mozilla) allows you to commit encrypted secrets to the Git repository. The ciphertext lives in Git alongside the application manifests; only the cluster (or a developer with the decryption key) can read the plaintext. This works particularly well with GitOps: Flux CD has native SOPS decryption support, so the GitOps agent decrypts secrets automatically when applying them to the cluster.
Policy as Code: Admission Controllers¶
GitOps ensures that what is in Git is applied to the cluster. But what prevents someone from submitting a manifest to Git (or directly to kubectl) that violates organizational policy — a Deployment without resource limits, a container running as root, a pod pulling from an unauthorized registry, or an image with no signature?
OPA (Open Policy Agent) with the Gatekeeper integration controller runs as a Kubernetes admission webhook. Every kubectl apply or API server mutation is intercepted; OPA evaluates the request against Rego policies. If the policy fails, the admission is denied. Policies are expressed as Kubernetes CRDs (ConstraintTemplate + Constraint).
Kyverno is a Kubernetes-native policy engine with a YAML-based policy syntax that many teams find more approachable than Rego. It supports both validation (deny non-compliant resources) and mutation (automatically add resource limits if not set, inject sidecar containers, copy secrets across namespaces).
The GitOps Loop Visualized¶
flowchart LR
subgraph DEVFLOW["Developer Workflow"]
DEV["Developer<br/>edits manifest<br/>or bumps image tag"] --> PR["Pull Request<br/>(review + approve)"]
PR --> MERGE["Merge to main"]
end
subgraph GIT["Git Repository<br/>(desired state)"]
MERGE --> REPO["Updated Manifests<br/>/ Helm values<br/>/ Kustomize overlays"]
end
subgraph CLUSTER["Kubernetes Cluster"]
ARGO["ArgoCD / Flux<br/>reconciliation loop<br/>(every 3 min or webhook)"] --> DIFF{"Diff:<br/>Git vs. Live?"}
DIFF -->|"In sync"| WATCH["Continue watching"]
DIFF -->|"Out of sync"| APPLY["kubectl apply<br/>(converge to Git)"]
APPLY --> LIVE["Live cluster state<br/>matches Git"]
DRIFT["Manual kubectl edit<br/>(drift)"] -.->|"selfHeal=true"| DIFF
end
REPO -->|"webhook / poll"| ARGO
WATCH --> ARGO
style DEVFLOW fill:#1e3a5f,color:#e2e8f0
style GIT fill:#1a3a2e,color:#e2e8f0
style CLUSTER fill:#2a1e3a,color:#e2e8f0 Consulting Lens: The Enterprise Audit Trifecta
IaC + GitOps + secrets management is what regulated industries mean when they ask for “governance.” When a bank or healthcare organization’s auditors ask “prove who changed the production firewall rule on the 14th and why,” the answer with this stack is a specific git commit: authored by a named engineer, approved by two reviewers, linked to a ticket, merged at 14:32 UTC. That is an immensely strong audit position that no console-click-based approach can match.
The selling proposition for IaC + GitOps has three pillars: reproducibility (rebuild any environment from code, any time, identically), traceability (every infrastructure change is a reviewed, attributed, linked commit), and least-privilege secrets handling (no one touches plaintext secrets; the cluster fetches them from the vault at runtime). Together they close most of the operational risk gaps that enterprise audit frameworks target.
Knowledge check
InterviewIn a GitOps workflow using Argo CD, how is a change deployed to production and then rolled back if a problem is detected?
- A pipeline job runs
kubectl rollout undoto revert the deployment to the previous replica set, bypassing Git. - An engineer uses the ArgoCD UI to manually sync from the previous commit hash, which updates the cluster but not the Git repository.
- A pull request is merged to Git to deploy; Argo CD detects the diff and reconciles the cluster to the new state. Rollback is a revert commit in Git; Argo CD reconverges the cluster to the previous desired state automatically.
- Argo CD deploys using a canary release; if metrics fail, it automatically reverts by re-running the previous CI pipeline and pushing a new image.
Answer
A pull request is merged to Git to deploy; Argo CD detects the diff and reconciles the cluster to the new state. Rollback is a revert commit in Git; Argo CD reconverges the cluster to the previous desired state automatically.
In GitOps, Git is the source of truth. A deployment is a merge to the target branch; ArgoCD or Flux detects the diff between what is in Git and what is live in the cluster, then applies the Git state. Rollback is a git revert of the offending commit (or a PR that reverts it); the agent sees the new HEAD is the reverted state and reconverges the cluster. The audit trail is complete: the rollback commit is attributed, timestamped, and linked. Manual operations like kubectl rollout undo would be overwritten by the GitOps agent on the next sync cycle if selfHeal is enabled.
Exercise
Exercise 13.4 · Terraform S3 Backend and ArgoCD Application Complete two tasks:
- Write the Terraform backend configuration to store state in an S3 bucket named
corp-tf-state, key pathservices/auth/terraform.tfstate, regioneu-west-1, with encryption and a DynamoDB lock table namedtf-locks. - Write an ArgoCD
Applicationmanifest that watches theapps/auth/overlays/prodpath ofhttps://github.com/corp/k8s-configon themainbranch, deploys to theauthnamespace in the in-cluster server, with automated sync, pruning, and self-healing enabled.
Show solution
Key Takeaways
- IaC (Terraform, Pulumi, CDK) makes infrastructure reproducible, diffable, and auditable — the alternative (clicking in a console) cannot be reviewed, versioned, or reliably reproduced.
- Terraform remote state (S3 + DynamoDB, GCS, Terraform Cloud) must be used in team environments to prevent concurrent apply races and share the source of truth.
- Pulumi lets Python engineers write IaC in typed, testable Python — infrastructure code is refactorable and importable like any other Python module.
- GitOps (Argo CD / Flux) makes deployment a PR merge and rollback a commit revert; the in-cluster agent continuously reconciles live state to Git, with drift auto-correction.
- Never commit plaintext secrets — even to private repos; use External Secrets Operator (sync from a secrets manager into K8s), SOPS (encrypt ciphertext in Git), or Sealed Secrets (cluster-key-encrypted K8s Secrets).
- IaC + GitOps + secrets management is the enterprise audit trifecta: reproducibility, traceability, and least-privilege secrets handling — the answer to “prove who changed production and when” is “it’s the Git history.”