Automating AWS CodePipeline with Terraform and GitHub Actions
Manual pipeline configuration drifts fast. A junior admin tweaks a stage in the Sydney console, a contractor changes an IAM role in Melbourne, and a year later nobody can reproduce the original setup. Treating the entire delivery chain as code eliminates that drift and gives auditors a clean history. HashiCorp Terraform owns the pipeline topology while GitHub Actions drives day-to-day orchestration, and the two together replace fragile console clicking with repeatable, peer-reviewed change.
The split is deliberate. Resource declarations, IAM policies, S3 buckets for artefacts, and CodePipeline stage definitions all live in Terraform modules that version alongside application code. GitHub Actions picks up pull requests, runs terraform plan, and only applies changes after a second engineer approves. OIDC tokens replace long-lived access keys, which means there are no secrets to rotate when a laptop is lost in transit between Brisbane and Perth.
Defining pipeline infrastructure as code
Terraform's AWS provider exposes every CodePipeline resource as a declarative block: aws_codepipeline, aws_codepipeline_stage, aws_codebuild_project, and aws_codedeploy_app. A typical pattern wraps each application into its own module so a team in Adelaide can promote a release without touching the central platform repository. Variables control environment names, account IDs, and notification targets, while outputs feed back to the GitHub Actions workflow so jobs know which pipeline ARN to watch.
Reusable modules pay off quickly. A single pipeline module can standardise source stages, build images, and manual approval gates across dozens of repositories. Versioning the module in a private registry, or simply tagging the Git repository, lets platform engineers ship improvements without breaking downstream consumers. The same pattern works for cross-account deployments using aws_codepipeline_webhook and aws_codestarconnections_connection, which keep the source trigger inside AWS rather than reaching out to GitHub's public API for every change.
Pipeline stages and source integration
A CodePipeline consists of at least two stages: source and build. The source stage points at a GitHub repository through a CodeStar connection, pulling branch and commit metadata that downstream stages can reference. The build stage runs CodeBuild in a managed container, while optional deploy stages push artefacts to ECS, Lambda, or EC2. Each stage accepts an action block, an input_artifact, and an output_artifact, and Terraform expresses these relationships in plain HCL.
GitHub Actions handles the work that happens before the pipeline ever sees the code: running linters, unit tests, security scanners, and policy checks. When those pass, the workflow either commits a tag, opens a pull request against the release branch, or invokes the pipeline directly through the aws CLI authenticated via OIDC. This pattern is common in Melbourne consultancies that need fast feedback loops for fintech clients operating under APRA's CPS 234 information security requirements.
| Concern | Terraform | GitHub Actions |
|---|---|---|
| Pipeline topology | Defines stages, actions, transitions | Reads outputs, triggers events |
| IAM permissions | aws_iam_role, aws_iam_policy |
OIDC trust, scoped permissions: |
| Build execution | Delegates to aws_codebuild_project |
Hosts runners or calls Lambda |
| Change control | Pull requests on the IaC repo | Pull requests on application code |
| State history | S3 backend with versioning | Workflow run logs and artefacts |
Wiring GitHub Actions to AWS securely
Static AWS access keys in GitHub Secrets are a liability. The cleaner path is OpenID Connect, which lets GitHub Actions assume an IAM role in AWS using a short-lived token tied to the repository, branch, or environment. Terraform creates the OIDC provider with aws_iam_openid_connect_provider and grants a role AssumeRoleWithWebIdentity permissions scoped to specific token.actions.githubusercontent.com:sub claims. The workflow requests the token, exchanges it for AWS credentials, and never stores a secret.
Self-hosted runners suit Australian teams that must keep build artefacts inside the country for data sovereignty. A runner in the ap-southeast-2 Sydney region keeps unredacted logs local, and one in ap-southeast-4 Melbourne helps meet the Australian Privacy Principles when dealing with health or government workloads. Runner images live in their own Terraform module, so patching the base AMI is a single pull request instead of a fleet-wide manual sweep.
State management, locking and secrets
Terraform state in a team setting belongs in S3 with DynamoDB locking. The backend block points at a versioned bucket in the same region as the pipeline, and the lock table prevents two engineers from applying the same change simultaneously. Enable bucket key encryption with KMS and turn on access logging for audit trails that satisfy the ACSC Essential Eight mitigation strategies. For larger platform teams, Terraform Cloud or Enterprise adds policy-as-code through Sentinel, which can block a merge that forgets to tag an S3 artefact bucket.
Secrets take a different path. Database passwords, third-party API tokens, and signing keys live in AWS Secrets Manager or SSM Parameter Store, referenced from Terraform with data sources rather than hardcoded values. CodeBuild projects pull them at runtime through the task role, and CodePipeline passes them between stages as variables. Sensitive values never appear in GitHub Actions logs because the workflow marks them with ::add-mask:: or pipes them through a file the runner never echoes back.
For organisations managing multiple AWS accounts, federation through AWS IAM Identity Center for single sign-on to multiple accounts lets engineers move between dev, staging, and production from a single console session. Terraform assumes roles across accounts through provider blocks with assume_role, and GitHub Actions picks up the same roles through OIDC claims.
Running pipelines from Australia
Local context shapes the architecture more than most teams expect. Latency from Perth to the nearest AWS region in Sydney adds roughly 40 milliseconds, which matters for chatty CI jobs that fetch dependencies on every run. Caching Maven, npm, pip, and Docker layers inside an S3 bucket in ap-southeast-2 cuts minutes off each build and reduces egress charges billed in AUD. Teams on NBN or business-grade fibre should pin runner pools to the Sydney or Melbourne endpoints and avoid the temptation to fan out to Singapore or Tokyo just because it feels faster on paper.
Compliance pulls in the same direction. Pipelines that deploy to government or critical-infrastructure workloads may need IRAP-aligned controls, and pipeline logs stored in Australian regions simplify an assessment. Pairing Terraform's audit trail with the Notifiable Data Breaches scheme means a misconfigured S3 bucket shows up in code review long before it shows up in the regulator's inbox. The Australian Cyber Security Centre's Essential Eight maturity levels map neatly onto Terraform policy checks, and platform engineers in Canberra and Hobart are already codifying those rules as reusable modules that other teams can consume.