GitHub Actions Automation
Complete guide to GitHub Actions CI/CD automation including workflow syntax, jobs, steps, secrets, matrix builds, and self-hosted runners.
# GitHub Actions Automation
Introduction
GitHub Actions is GitHub's native CI/CD platform that enables developers to automate software workflows directly from their repositories. With GitHub Actions, you can build, test, package, release, and deploy code without leaving GitHub. It supports custom workflows written in YAML, integrates with GitHub's ecosystem, and provides hosted runners for Linux, macOS, and Windows.
This tutorial covers GitHub Actions workflow syntax, jobs, steps, secrets management, matrix builds, reusable workflows, self-hosted runners, and production deployment patterns.
Learning Objectives
By the end of this tutorial, you will be able to:
- Create GitHub Actions workflows with YAML syntax
- Configure jobs, steps, and dependencies
- Manage secrets and environment variables
- Implement matrix builds for multi-platform testing
- Create reusable workflows and composite actions
- Set up self-hosted runners
- Deploy applications to cloud platforms
- Secure GitHub Actions with permissions and OpenID Connect
Technical Background
Workflow Structure
flowchart TD
Event[Workflow Event] --> Trigger[Trigger Workflow]
Trigger --> Job1[Job 1]
Trigger --> Job2[Job 2]
Job1 --> Step1[Step 1]
Step1 --> Step2[Step 2]
Job1 -->|needs| Job3[Job 3]
Job2 -->|needs| Job3
subgraph Workflow File (.github/workflows/*.yml)
Event
Job1
Job2
Job3
endGitHub Actions Components
| Component | Description | |-----------|-------------| | Workflow | Automated process defined in YAML | | Event | Trigger that starts workflow (push, pull_request, schedule) | | Job | Set of steps executed on the same runner | | Step | Individual task (run command or use action) | | Action | Reusable unit of code (community or custom) | | Runner | Server that executes workflows | | Secret | Encrypted variable for sensitive data | | Artifact | File produced by workflow, shareable between jobs |
GitHub-Hosted Runners
| OS | Label | Included Software | |----|-------|-------------------| | Ubuntu | `ubuntu-latest` | Docker, Node.js, Python, Java, Go, Ruby, .NET | | Windows | `windows-latest` | Docker, Node.js, Python, Java, PowerShell | | macOS | `macos-latest` | Xcode, Node.js, Python, Java, Ruby, Go |
Prerequisites
- GitHub repository
- GitHub account with write access to repository
- Understanding of YAML syntax
- Application code to build and deploy
- Cloud platform account for deployment targets
Hardware and Software Requirements
| Component | Requirement | |-----------|-------------| | Repository | GitHub repository (public or private) | | Runner | GitHub-hosted or self-hosted | | CLI Tools | Git, Docker, cloud CLI (optional) | | Browser | Web browser for GitHub interface | | IDE | VS Code or any text editor |
Step-by-Step Implementation
Phase 1: Create First Workflow
#### Basic CI Workflow
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [develop]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build application
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7#### Workflow Trigger Events
on:
# Push to specific branches
push:
branches: [main, develop, 'feature/*']
tags: ['v*']
# Pull request events
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Scheduled (cron syntax)
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
# Manual trigger
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
version:
description: 'Version to deploy'
required: true
# Repository dispatch (external trigger)
repository_dispatch:
types: [deploy-command]Phase 2: Jobs and Steps
#### Multiple Jobs with Dependencies
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: echo "Deploying to production"#### Conditional Execution
jobs:
deploy-staging:
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to staging"
deploy-production:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to production"Phase 3: Secrets and Environment Variables
#### Repository Secrets
1. Go to repository **Settings** > **Secrets and variables** > **Actions** 2. Click **New repository secret** 3. Enter name and value (e.g., `AWS_ACCESS_KEY_ID`, `DOCKER_PASSWORD`) 4. Click **Add secret**
#### Using Secrets in Workflow
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
aws s3 cp dist/ s3://my-bucket/ --recursive#### Environment Secrets
jobs:
deploy-production:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: echo "Using production secrets: ${{ secrets.PRODUCTION_DB_PASSWORD }}"Phase 4: Matrix Builds
#### Multi-OS Testing
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20]
exclude:
- os: macos-latest
node-version: 18
steps:
- uses: actions/checkout@v4
- name: Set up Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test#### Multi-Architecture Builds
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [amd64, arm64]
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build for ${{ matrix.platform }}
run: |
docker build --platform linux/${{ matrix.platform }} \
-t myapp:${{ matrix.platform }} .Phase 5: Reusable Workflows and Composite Actions
#### Reusable Workflow
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy Workflow
on:
workflow_call:
inputs:
environment:
required: true
type: string
image-tag:
required: true
type: string
secrets:
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"#### Call Reusable Workflow
# .github/workflows/main-cicd.yml
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
image-tag: ${{ github.sha }}
secrets: inherit
deploy-production:
needs: deploy-staging
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
image-tag: ${{ github.sha }}
secrets: inherit#### Composite Action
# .github/actions/setup-node/action.yml
name: Setup Node.js
description: Set up Node.js with caching
inputs:
node-version:
required: true
description: Node.js version
cache:
required: false
default: 'npm'
description: Cache type
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: ${{ inputs.cache }}
- run: npm ci
shell: bash#### Use Composite Action
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node
with:
node-version: '20'
- run: npm run buildPhase 6: Self-Hosted Runners
#### Add Self-Hosted Runner
1. Go to repository **Settings** > **Actions** > **Runners** 2. Click **New self-hosted runner** 3. Select OS and architecture 4. Follow commands to download, configure, and run runner
# Download runner
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.311.0.tar.gz -L \
https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz
# Configure runner
./config.sh --url https://github.com/username/repo --token TOKEN
# Run runner
./run.sh#### Use Self-Hosted Runner
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- run: echo "Running on self-hosted runner"#### Runner Groups and Labels
jobs:
build:
runs-on: [self-hosted, linux, x64]
steps:
- run: echo "Running on labeled self-hosted runner"Phase 7: Cloud Deployment
#### Deploy to AWS ECS
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: myapp
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster my-cluster \
--service my-service \
--force-new-deployment#### Deploy to Kubernetes
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Deploy application
run: |
kubectl set image deployment/my-app \
my-app=myregistry.com/myapp:${{ github.sha }} \
-n production
kubectl rollout status deployment/my-app -n production --timeout=300sPhase 8: Security
#### OpenID Connect (OIDC) for AWS
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: us-east-1#### Workflow Permissions
permissions:
contents: read
pull-requests: write
id-token: writeExpected Output
- Automated CI/CD workflows triggered by GitHub events
- Matrix builds testing across multiple platforms
- Secrets securely stored and injected
- Reusable workflows reducing duplication
- Self-hosted runners for specialized workloads
- Secure deployments using OIDC
Verification Procedures
| Check | Method | Expected Result | |-------|--------|-----------------| | Workflow triggers | Push code to branch | Workflow starts automatically | | Job completion | Check Actions tab | All jobs pass | | Artifact upload | Download artifact from workflow run | Files downloadable | | Secret injection | Check debug log | Secrets masked as `***` | | Deployment | Check target environment | Application deployed |
Troubleshooting Scenarios
| Issue | Diagnosis | Resolution | |-------|-----------|------------| | Workflow not triggering | Wrong event or branch filter | Check `on` configuration | | Permission denied | Missing repository permissions | Add secret or adjust permissions | | Runner offline | Self-hosted runner not running | Start runner service | | Secret not available | Secret not set for environment | Add secret to environment | | Matrix build fails | Incompatible OS/version combination | Check exclude list | | Action not found | Wrong action version or path | Verify action reference |
Best Practices
- **Use specific action versions:** Pin actions to specific versions (e.g., `@v4`)
- **Minimize permissions:** Grant only required permissions to workflows
- **Cache dependencies:** Use actions/cache for faster builds
- **Use reusable workflows:** Reduce duplication across workflows
- **Store secrets securely:** Never commit secrets to repository
- **Use OIDC:** Avoid long-lived AWS credentials
- **Document workflows:** Add comments explaining complex logic
- **Test workflow changes:** Test in pull request before merging to main
Security Considerations
- **Never log secrets:** GitHub automatically masks secrets, but avoid printing them
- **Use CODEOWNERS:** Require reviews from appropriate teams
- **Protect branches:** Require PR reviews and status checks
- **Audit workflow changes:** Review changes to `.github/workflows/`
- **Limit runner access:** Self-hosted runners should be isolated
- **Use environment protection rules:** Require approval for production deployments
Conclusion
GitHub Actions provides a powerful, flexible CI/CD platform integrated directly into GitHub. By mastering workflow syntax, secrets management, matrix builds, reusable workflows, and security best practices, teams can automate their entire software delivery pipeline. The combination of GitHub-hosted runners, reusable workflows, and OIDC security makes GitHub Actions suitable for projects of any size.
References
- GitHub Actions Docs: https://docs.github.com/en/actions
- Workflow Syntax: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
- Actions Marketplace: https://github.com/marketplace?type=actions
- Self-Hosted Runners: https://docs.github.com/en/actions/hosting-your-own-runners
- OpenID Connect: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect