Production GitOps: CI/CD Pipelines, TFLint, Trivy & Policy as Code
Automate 'plan' on Pull Requests and 'apply' on main branch merge with zero local execution.
Authenticate CI runners to AWS securely using short-lived OpenID Connect JWT tokens.
Enforce security standards before deployment using TFLint, Trivy, and Checkov.
The Big Idea
In an elite engineering organization, developers never run terraform apply from their personal laptops. Local execution creates security liabilities, relies on long-lived access keys, and prevents transparent peer review.
GitOps turns your git repository into the single source of truth and control plane for your infrastructure. Every modification starts as a pull request. Automated CI/CD pipelines run code formatters, static security scanners, and generate speculative plans posted as PR comments for peer review.
Once approved and merged into the main branch, a production pipeline applies the changes using short-lived OIDC (OpenID Connect) tokens: zero permanent IAM secrets stored in GitHub. Combined with lifecycle rules like prevent_destroy and create_before_destroy, your infrastructure achieves enterprise-grade resilience.
Jargon Buster: Plain-English Translations
- GitOps: An operational framework that uses Git repositories as the single source of truth for infrastructure and application code.
- OIDC (OpenID Connect): A federated authentication protocol that allows GitHub Actions to assume AWS IAM roles using temporary, cryptographically signed tokens.
- Policy as Code (PaC): Writing security, compliance, and cost guardrails as code (e.g. blocking any S3 bucket with public read enabled).
- create_before_destroy: A lifecycle meta-argument that creates a replacement resource before tearing down the old one, preventing zero-downtime gaps.
Production GitHub Actions GitOps Pipeline & Lifecycle Rules
# .github/workflows/terraform.yml
name: "Terraform Production GitOps Pipeline"
on:
pull_request:
branches: [ "main" ]
push:
branches: [ "main" ]
permissions:
id-token: write # Required for AWS OIDC authentication
contents: read
pull-requests: write
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Authenticate to AWS via OIDC (No Static Keys)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-tf-role
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.5
- name: Lint and Format Check
run: |
terraform fmt -check
- name: Security Vulnerability Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
hide-progress: true
severity: 'CRITICAL,HIGH'
- name: Terraform Init
run: terraform init
- name: Terraform Plan (on PR)
if: github.event_name == 'pull_request'
run: terraform plan -no-color -out=tfplan
- name: Terraform Apply (on Main Merge)
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve
Beginner Traps & Gotchas
1. Storing long-lived AWS_ACCESS_KEY_ID in GitHub Secrets
The Gotcha: Generating permanent IAM admin keys and pasting them into repository secrets, risking credential theft.
The Fix: Always use OIDC role assumption. The GitHub runner exchanges an ephemeral token for a 15-minute temporary AWS session.
2. Omitting prevent_destroy on production databases
The Gotcha: Accidentally destroying production RDS instances due to refactoring or misconfigured plan.
The Fix: Add 'lifecycle { prevent_destroy = true }' to all mission-critical data stores. Terraform will reject any plan attempting to delete them.