Introduction
GitHub Actions is a CI/CD platform that allows you to automate workflows directly in your GitHub repository. For infrastructure engineers, GitHub Actions can automate Terraform deployments, run Ansible playbooks, build Docker images, validate configurations, and enforce security policies — all triggered by code changes. This guide teaches you to build production-grade infrastructure pipelines with GitHub Actions.
Core Concepts
# .github/workflows/infra-deploy.yml
name: Infrastructure Deploy
# Triggers: when does this run?
on:
push:
branches: [main]
paths:
- 'terraform/**' # Only when terraform files change
pull_request:
branches: [main]
paths:
- 'terraform/**'
workflow_dispatch: # Manual trigger button in UI
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options: [staging, production]
jobs:
validate:
name: Validate Terraform
runs-on: ubuntu-latest # GitHub-hosted runner
# Set permissions for this job
permissions:
contents: read
pull-requests: write # To comment on PRs
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.6.0"
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
- name: Terraform Format Check
run: terraform fmt -check -recursive
working-directory: ./terraform
- name: Terraform Init
run: terraform init
working-directory: ./terraform/environments/staging
- name: Terraform Validate
run: terraform validate
working-directory: ./terraform/environments/staging
- name: Terraform Plan
id: plan
run: terraform plan -out=tfplan -no-color
working-directory: ./terraform/environments/staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Comment Plan on PR
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `### Terraform Plan Output${{ steps.plan.outputs.stdout }}
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})Terraform Apply Workflow
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: validate # Runs after validate job
if: github.ref == 'refs/heads/main' # Only on main branch
environment:
name: production # Requires manual approval in GitHub
url: https://app.company.com
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init and Apply
run: |
terraform init
terraform apply -auto-approve -input=false
working-directory: ./terraform/environments/production
env:
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
TF_VAR_api_key: ${{ secrets.EXTERNAL_API_KEY }}Docker Build and Push
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ['v*.*.*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix=sha-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha # GitHub Actions cache
cache-to: type=gha,mode=maxSecurity Scanning in CI
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Scan Terraform for security issues
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
soft_fail: false
# Scan Docker image for CVEs
- name: Scan Docker image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
# Check for secrets in code
- name: Detect secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
head: HEADAnsible Deployment Workflow
name: Deploy with Ansible
on:
workflow_dispatch:
inputs:
target_hosts:
description: 'Target hosts pattern'
default: 'web-servers'
playbook:
description: 'Playbook to run'
default: 'deploy.yml'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H ${{ secrets.BASTION_HOST }} >> ~/.ssh/known_hosts
- name: Run Ansible Playbook
uses: dawidd6/action-ansible-playbook@v2
with:
playbook: ${{ inputs.playbook }}
directory: ./ansible
key: ${{ secrets.DEPLOY_SSH_KEY }}
inventory: |
[web-servers]
web-01.company.com
web-02.company.com
options: |
--extra-vars "version=${{ github.sha }}"
--tags deployReusable Workflows
# .github/workflows/reusable-terraform.yml
name: Reusable Terraform
on:
workflow_call:
inputs:
environment:
required: true
type: string
tf_directory:
required: true
type: string
secrets:
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
jobs:
terraform:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: |
terraform init
terraform plan -out=tfplan
terraform apply tfplan
working-directory: ${{ inputs.tf_directory }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}# Calling the reusable workflow
name: Deploy All Environments
on:
push:
branches: [main]
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-terraform.yml
with:
environment: staging
tf_directory: terraform/environments/staging
secrets: inherit
deploy-production:
uses: ./.github/workflows/reusable-terraform.yml
needs: deploy-staging
with:
environment: production
tf_directory: terraform/environments/production
secrets: inheritGitHub Actions transforms infrastructure management from manual, error-prone processes into auditable, reproducible pipelines. Start with simple validation workflows, add security scanning, then build full deployment pipelines with approval gates for production.
