Overview
The starter kit's CI/CD pipeline automates infrastructure validation, security scanning, and deployment through GitHub Actions. The workflow uses OpenID Connect (OIDC) for secure, keyless authentication to AWS.
Note: All workflows discussed on this page are automatically generated by the make setup command. For installation instructions, see the Install guide.
Pipeline architecture
The starter kit includes four workflow files in .github/workflows/:
.github/workflows/
├── tflint-scan.yml # Reusable linting workflow
├── checkov-scan.yml # Reusable security scan workflow
├── terraform-plan-pr-comment.yml # Reusable plan comment workflow
└── terraform-deploy-staging.yml # Generated environment-specific workflow
Reusable workflows
Three workflows are designed to be called from environment-specific workflows:
- tflint-scan.yml - Validates Terraform code quality and best practices
- checkov-scan.yml - Scans for security misconfigurations
- terraform-plan-pr-comment.yml - Posts Terraform plan output to PR comments
Environment-specific workflows
When you run make setup, the wizard generates a deployment workflow for each environment (e.g., terraform-deploy-staging.yml, terraform-deploy-production.yml). These workflows:
- Call the three reusable workflows
- Contain environment-specific configuration (AWS account ID, region, state bucket)
- Handle the complete deployment lifecycle
Environment variables
Each generated workflow includes environment variables with defaults from your setup:
env:
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID || '123456789012' }}
AWS_REGION: ${{ vars.AWS_REGION || 'eu-west-1' }}
GITHUB_ACTIONS_ROLE_NAME: ${{ vars.GITHUB_ACTIONS_ROLE_NAME || 'GitHubActionsServiceRole-Terraform' }}
ENVIRONMENT: staging
TF_WORKING_DIR: environments/staging
TF_STATE_BUCKET: ${{ vars.TF_STATE_BUCKET || 'terraform-state-123456789012-eu-west-1' }}
Note: Values are embedded as defaults during setup. You can override these by creating GitHub repository variables.
Workflow triggers
The environment-specific workflows (e.g., terraform-deploy-staging.yml) trigger on three events:
on:
push:
branches:
- main
paths:
- "environments/staging/**"
- "modules/**"
- ".github/workflows/terraform-deploy-staging.yml"
pull_request_target:
branches:
- main
workflow_dispatch:
Push to main
Triggers when changes are merged to the main branch that affect:
- The environment directory (
environments/staging/**) - Shared modules (
modules/**) - The workflow file itself
Jobs executed:
- TFLint scan
- Checkov security scan
- Terraform check (format, init, validate)
- Terraform apply
Pull request
Triggers on pull requests targeting the main branch (no path filtering for PRs):
Jobs executed:
- TFLint scan
- Checkov security scan
- Terraform check (format, init, validate)
- Terraform plan
- Plan comment posted to PR
Manual dispatch
Allows manual workflow execution from GitHub Actions UI or CLI:
gh workflow run terraform-deploy-staging.yml
Job breakdown
The pipeline consists of six jobs that run sequentially:
tflint → checkov → terraform-check → terraform-plan/apply → plan-comment
1. TFLint scan
Calls the reusable tflint-scan.yml workflow to validate Terraform code quality:
tflint:
name: TFLint Scan
uses: ./.github/workflows/tflint-scan.yml
What it does:
- Checks out code
- Caches TFLint plugin directory for faster runs
- Installs TFLint
- Initializes TFLint with plugins from
.tflint.hcl - Runs
tflint -f compact --recursiveon all Terraform files - Posts results to GitHub Step Summary
Configuration: .tflint.hcl in the repository root
Local testing: Run make lint to execute the same checks locally. See the Makefile reference for details.
2. Checkov security scan
Calls the reusable checkov-scan.yml workflow to scan for security misconfigurations:
checkov:
name: Checkov Security Scan
uses: ./.github/workflows/checkov-scan.yml
with:
working_directory: "environments/staging"
soft_fail: true
What it does:
- Checks out code
- Runs Checkov with framework set to
terraform - Downloads external modules for comprehensive scanning
- Uses
.checkov.ymlfor configuration - Posts security scan results to GitHub Step Summary
Key settings:
soft_fail: true- Workflow continues even if security issues are founddownload_external_modules: true- Scans module dependencies
Configuration: .checkov.yml in the repository root
Local testing: Run make security-scan to execute the same checks locally. See the Makefile reference for details.
3. Terraform check
Validates Terraform configuration and formatting:
terraform-check:
name: Terraform Check
runs-on: ubuntu-latest
needs: [tflint, checkov]
defaults:
run:
working-directory: "environments/staging"
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validate
What it does:
- Checks out code (using PR head SHA for pull requests)
- Installs Terraform
- Validates code formatting with
terraform fmt -check -recursive - Initializes Terraform without backend (
-backend=falsefor validation only) - Validates configuration syntax and logic
Note: needs: [tflint, checkov] means this job waits for both scans to complete first.
Local equivalent: Run make validate-full ENV=staging to execute all these checks locally. See the Makefile reference for details.
4. Terraform plan (PR only)
Generates an execution plan for pull request review:
terraform-plan:
name: Terraform Plan
runs-on: ubuntu-latest
needs: terraform-check
if: github.event_name == 'pull_request_target'
defaults:
run:
working-directory: "environments/staging"
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/${{ env.GITHUB_ACTIONS_ROLE_NAME }}
aws-region: ${{ env.AWS_REGION }}
role-session-name: GitHubActions-Terraform-Plan-Staging
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: |
terraform init \
-backend-config="bucket=${{ env.TF_STATE_BUCKET }}" \
-backend-config="key=environments/staging/terraform.tfstate" \
-backend-config="region=${{ env.AWS_REGION }}" \
-backend-config="use_lockfile=true"
- name: Terraform Plan
run: terraform plan -out=tfplan.binary
continue-on-error: true
- name: Save Plan Artifact
if: always()
uses: actions/upload-artifact@v5
with:
name: terraform-plan-artifact
path: ${{ env.TF_WORKING_DIR }}/tfplan.binary
retention-days: 1
Key features:
- Only runs on pull requests (
if: github.event_name == 'pull_request_target') - Uses OIDC for secure, keyless AWS authentication
- Backend configuration passed dynamically from environment variables
continue-on-error: trueensures artifact is saved even if plan fails- Plan binary saved for 1 day
5. Plan comment (PR only)
Posts the Terraform plan to the pull request as a comment:
plan-comment:
name: Post Plan Comment
needs: terraform-plan
if: github.event_name == 'pull_request_target'
uses: ./.github/workflows/terraform-plan-pr-comment.yml
with:
planfile: tfplan.binary
working-directory: "environments/staging"
aws-region: eu-west-1
environment: staging
The reusable workflow (terraform-plan-pr-comment.yml):
- Downloads the plan artifact from the previous job
- Initializes Terraform with
-backend=false - Uses the
towardsthecloud/terraform-plan-pr-commenter@v1action to:- Convert binary plan to readable format
- Post formatted plan as PR comment
- Include header: "Terraform Plan for staging in eu-west-1"
Benefits:
- Reviewers see infrastructure changes before merge
- No need to run
terraform planlocally - Plan preserved in PR history
6. Terraform apply (push to main only)
Applies changes to AWS infrastructure after merging to main:
terraform-apply:
name: Terraform Apply
runs-on: ubuntu-latest
needs: terraform-check
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: staging
defaults:
run:
working-directory: "environments/staging"
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/${{ env.GITHUB_ACTIONS_ROLE_NAME }}
aws-region: ${{ env.AWS_REGION }}
role-session-name: GitHubActions-Terraform-Apply-Staging
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: |
terraform init \
-backend-config="bucket=${{ env.TF_STATE_BUCKET }}" \
-backend-config="key=environments/staging/terraform.tfstate" \
-backend-config="region=${{ env.AWS_REGION }}" \
-backend-config="use_lockfile=true"
- name: Terraform Apply
run: terraform apply -auto-approve
- name: Terraform Output
if: success()
run: |
echo "### Terraform Outputs :rocket:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
terraform output >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Deployment Status
if: always()
run: |
if [ $? -eq 0 ]; then
echo "✅ Deployment to staging environment successful!" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Deployment to staging environment failed!" >> $GITHUB_STEP_SUMMARY
fi
Key features:
- Only runs on pushes to
mainbranch environment: stagingenables GitHub environment protection (can require manual approval)- Uses OIDC for authentication with session name
GitHubActions-Terraform-Apply-Staging - Runs
terraform apply -auto-approve(no manual confirmation in CI/CD) - Posts Terraform outputs to GitHub Step Summary
- Shows deployment status (success/failure)
OIDC authentication
The workflows use OpenID Connect (OIDC) for secure, keyless authentication to AWS:
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/${{ env.GITHUB_ACTIONS_ROLE_NAME }}
aws-region: ${{ env.AWS_REGION }}
role-session-name: GitHubActions-Terraform-Apply-Staging
How it works:
- GitHub generates a JWT token for the workflow
- Workflow assumes the IAM role using the JWT
- AWS validates the token against the OIDC provider
- AWS STS returns temporary credentials
- Workflow uses credentials to run Terraform commands
Security benefits:
- No AWS credentials stored in GitHub Secrets
- Temporary credentials automatically expire
- Repository-scoped access via trust policy
- Full audit trail via CloudTrail
Required permissions in the workflow:
permissions:
id-token: write
contents: read
pull-requests: write
Learn more: How to set up GitHub OIDC federation for AWS deployments
Environment protection
The environment: staging field in the terraform-apply job enables GitHub environment protection:
terraform-apply:
environment: staging # Enables protection rules
To require manual approval before deployment:
- Navigate to Settings → Environments in your GitHub repository
- Create or select the environment (e.g.,
production,staging) - Enable Required reviewers
- Add team members who must approve deployments
When protection is enabled, the workflow will pause at the terraform-apply job and wait for approval from designated reviewers before proceeding.
Path-based filtering
The workflow only runs when relevant files change:
on:
push:
paths:
- "environments/staging/**"
- "modules/**"
- ".github/workflows/terraform-deploy-staging.yml"
Benefits:
- Workflows don't run for unrelated changes
- Changing one environment doesn't trigger workflows for other environments
- Faster CI/CD with fewer unnecessary runs
Artifacts
The terraform-plan job saves the plan as an artifact:
- name: Save Plan Artifact
uses: actions/upload-artifact@v5
with:
name: terraform-plan-artifact
path: ${{ env.TF_WORKING_DIR }}/tfplan.binary
retention-days: 1
This artifact is:
- Downloaded by the
plan-commentjob to post the plan to the PR - Retained for 1 day
- Can be downloaded manually from the workflow run for inspection
Next steps
- Learn about Local Development workflows
- Explore Environments for multi-environment setup
- Read about the OIDC provider module