DEV Community

Renato Mendoza
Renato Mendoza

Posted on

One-Click Deployments #1: Bootstrap a GitOps-Ready AWS Terraform Backend with GitHub Actions (OIDC)

Welcome to the first post in my One-Click Deployments series. In this series, we build infrastructure with as little manual setup as possible.

In this post, you will bootstrap a secure AWS Terraform backend in one command. No more storing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in GitHub secrets. We use GitHub Actions + OpenID Connect (OIDC) to assume an AWS IAM role at runtime, avoiding long-lived credentials entirely.


What You Get

A single terraform apply in infra/bootstrap creates:

  • S3 bucket for Terraform state
    • Versioning enabled, SSE-KMS with a customer-managed key, bucket key enabled
    • Public access blocked, TLS-only bucket policy
    • Lifecycle to expire noncurrent versions after 365 days
  • DynamoDB table for state locking on-demand
  • KMS key with rotation enabled, alias created
  • GitHub OIDC provider in AWS
  • IAM role for GitHub Actions with least-privilege backend access
  • backend.generated.hcl written for you

Why This Matters

Setting up a secure Terraform backend is usually tedious:

  1. Create and secure an S3 bucket
  2. Add DynamoDB for state locking
  3. Provision and wire a KMS key
  4. Write and distribute backend config
  5. Create an IAM role with correct trust
  6. Configure OIDC

This template does it in one go and wires your GitOps workflow on branches env/dev, env/staging, env/prod.


Repository Structure

one-click-aws-terraform-backend-gitops-oidc/
├── infra/
│   ├── bootstrap/      # Creates S3, DynamoDB, KMS, OIDC, IAM role
│   └── envs/           # Root stack using the generated backend
└── .github/
    └── workflows/
        └── terraform.yaml  # GitOps workflow
Enter fullscreen mode Exit fullscreen mode

Prerequisites

  • AWS account and credentials locally with permissions for IAM, S3, DynamoDB, and KMS
  • AWS CLI configured
  • Terraform 1.6 or newer
  • A GitHub repo with environments: dev, staging, prod

Quick Start

  1. Clone the repository

    git clone https://github.com/rnato35/one-click-aws-terraform-backend-gitops-oidc.git
    
  2. Initialize and apply Terraform

    cd ./one-click-aws-terraform-backend-gitops-oidc/infra/bootstrap && terraform init
    terraform apply -var 'github_org=<your-gh-org-or-user>' -var 'github_repo=<your-github-repo>'
    

    Notes:

    • The current trust policy matches repo:<org>/<repo>:* by default.
    • Branch filtering is enforced by GitHub Environments and branch protections.
  3. Copy outputs to GitHub variables and secrets

    Repository variables:

    TF_BACKEND_BUCKET=<bucket_name>
    TF_BACKEND_REGION=<region>
    TF_BACKEND_DDB_TABLE=<dynamodb_table_name>
    TF_BACKEND_KMS_KEY_ID=<kms_key_arn>
    

    Environment secrets (dev, staging, prod):

    AWS_ROLE_ARN=<github_role_arn_from_outputs>
    

GitOps Flow Explained

Branch Mapping

  • env/dev → GitHub environment dev
  • env/staging → GitHub environment staging
  • env/prod → GitHub environment prod

Pull requests targeting env/*:

  • Run plan only
  • Selects workspace based on PR base branch
  • Uses per-env tfvars (dev, staging, prod)
  • Posts plan in logs

Pushes to env/*:

  • Run apply
  • Selects workspace and env tfvars automatically
  • Production (env/prod) requires a manual approval step before apply

    • Approvers can be changed in:
    .github/workflows/terraform.yaml
    

Credentials

  • Uses OIDC to assume AWS_ROLE_ARN from the target GitHub environment

Backend Wiring

  • terraform init receives -backend-config values from repository variables TF_BACKEND_*
  • The same remote state is used locally via backend.generated.hcl

Recommended Guardrails

  • Protect env/staging and env/prod branches
  • Require reviewers in GitHub Environments staging and prod
  • Scope the IAM role to least privilege only the resources your infra needs

What the Bootstrap Creates in Detail

KMS Key

  • enable_key_rotation = true
  • 7-day deletion window
  • Alias: alias/<name_prefix>-tfstate

S3 Bucket for State

  • Versioning on
  • SSE-KMS using the CMK, bucket key enabled
  • Public access block on
  • TLS-only bucket policy
  • Lifecycle to expire noncurrent versions after 365 days

DynamoDB Table for Locks

  • PAY_PER_REQUEST

backend.generated.hcl Content

  • bucket, key = global/terraform.tfstate, region, encrypt, kms_key_id, dynamodb_table, acl = private, workspace_key_prefix = envs

GitHub OIDC Provider and IAM Role

  • Trust: repo:<org>/<repo>:* with aud = sts.amazonaws.com
  • Inline policy for backend access: S3 objects + List, DynamoDB lock item CRUD, KMS use for state
  • Managed policy attachments via github_oidc_managed_policy_arns
  • Inline policies loaded from Policy/*.json

Important default: All JSON policies found in infra/bootstrap/Policy are attached as inline policies. The repository includes a couple of example policies you can adapt to your needs:

  • Policy/TerraformAdminNoBilling.json
  • Policy/TerraformBillingAccess.json

Review and remove or replace these files to meet your least-privilege needs before applying in production.


Call to Action

Try the repo and deploy your backend in one click:

GitHub Repository

Top comments (0)