GitHub Actions does not need a permanent AWS access key to deploy infrastructure. A cleaner pattern is to let GitHub present an OpenID Connect token to AWS STS and assume a tightly scoped IAM role for the duration of the job.
The deployment path
GitHub push → GitHub Actions → OIDC token → AWS STS → IAM role → Terraform plan/apply The important shift is that GitHub stores no long-lived AWS secret. AWS decides whether the token is trusted, then issues temporary credentials.
What actually needs to exist
- An AWS IAM OIDC provider for GitHub.
- An IAM role whose trust policy accepts the repository identity you intend to deploy from.
- Repo-scoped permissions for the AWS resources Terraform should manage.
- Remote Terraform state in a durable, encrypted S3 bucket.
- A workflow with
id-token: writepermission and an AWS credentials step that assumes the role.
The two failure modes worth checking first
1. The trust policy does not match GitHub's subject claim
OIDC failures are often not an AWS-permission problem at all. The token's subject must match the condition in the role trust policy. Inspect the actual repository identity format you are receiving instead of assuming a name-only subject string.
2. Terraform fails before AWS authentication matters
A pipeline can be perfectly configured for OIDC and still die on terraform fmt -check, validation, packaging, or backend initialization. Keep those steps explicit so you can identify whether a failure is formatting, Terraform state, OIDC trust, IAM authorization, or the application itself.
Keep the role reusable, but not universal
A deployment role can support more than one stack without becoming an account-wide administrator. Prefer predictable resource-name prefixes and explicit service permissions. If a second application needs a new AWS service, add that permission deliberately rather than starting with *.
State is infrastructure too
Remote state is not optional once a pipeline is applying Terraform. Version the state bucket, encrypt it, block public access, require TLS, and use Terraform's supported locking mechanism. The bootstrap layer that creates that backend also needs a recovery story—local-only bootstrap state is still a single point of operational pain.
The short version
Use OIDC for identity, STS for temporary credentials, Terraform remote state for continuity, and a repo-scoped IAM role for deployment. When it fails, check formatting and backend initialization before assuming the cloud credentials are broken.
Editorial note: this guide is based on infrastructure patterns LionsTooth Tech has implemented directly, not on a simulated benchmark.