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:
- Create and secure an S3 bucket
- Add DynamoDB for state locking
- Provision and wire a KMS key
- Write and distribute backend config
- Create an IAM role with correct trust
- 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
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
-
Clone the repository
git clone https://github.com/rnato35/one-click-aws-terraform-backend-gitops-oidc.git
-
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.
- The current trust policy matches
-
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 environmentdev
-
env/staging
→ GitHub environmentstaging
-
env/prod
→ GitHub environmentprod
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 variablesTF_BACKEND_*
- The same remote state is used locally via
backend.generated.hcl
Recommended Guardrails
- Protect
env/staging
andenv/prod
branches - Require reviewers in GitHub Environments
staging
andprod
- 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>:*
withaud = 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)