What is GitHub Actions? CI/CD Workflows Explained
GitHub Actions is a CI/CD platform built directly into GitHub. You define automation workflows in YAML files stored in .github/workflows/ inside your repository. When something happens -- a push, a pull request, a tag, a scheduled time -- GitHub runs those workflows on its managed infrastructure. No server to set up, no external service to configure, no webhook plumbing required.
Actions launched in 2019 and moved rapidly from novelty to default choice for teams already working in GitHub. Understanding it is foundational to modern CI/CD practice and tightly linked to how Docker images get built and shipped in most teams today.
Core Concepts
A workflow is a YAML file in .github/workflows/. A repository can have many workflows. Each workflow defines when it runs (on:) and what it does (jobs:).
A job is a unit of work that runs on a single runner. Jobs in the same workflow run in parallel by default. You can make jobs depend on each other with needs:.
A step is an individual task inside a job -- either a shell command (run:) or a prebuilt action (uses:). Steps in a job run sequentially on the same runner.
A runner is the machine that executes a job. ubuntu-latest is the most common choice for Linux workloads.
A Real Deployment Workflow
Here is a complete workflow that tests a Node.js application, builds a Docker image, and deploys it to AWS ECS on every push to main:
# .github/workflows/deploy.yml
name: Test, Build, Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
AWS_REGION: eu-west-1
ECR_REPOSITORY: myapp
ECS_SERVICE: myapp-service
ECS_CLUSTER: production
jobs:
test:
name: Run Tests
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 tests
run: npm test -- --ci
deploy:
name: Build and Deploy
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
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: ${{ env.AWS_REGION }}
- name: Log in to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image to ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
Several things worth noting: needs: test ensures the deploy job only starts if tests pass. if: github.ref == 'refs/heads/main' prevents deployments from pull request branches. ${{ github.sha }} tags the Docker image with the exact commit hash, making every image traceable to its source code.
Matrix Builds
Matrix builds let you run a job across multiple configurations in parallel -- useful for testing against multiple Node.js versions or multiple operating systems:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci && npm test
This generates six parallel jobs -- one for each OS and Node version combination. A failure in any single combination fails the matrix.
Marketplace Actions
The GitHub Actions Marketplace has thousands of prebuilt actions for common tasks -- AWS deployments, Slack notifications, Docker operations, Terraform plans. Using actions/checkout@v4 rather than writing a git clone command yourself is one example. Always pin actions to a specific version tag or commit SHA rather than @main or @latest -- this prevents your pipeline from breaking silently when an action is updated.
Secrets Management
Secrets are stored in repository or organisation settings and injected into workflow runs. They are never printed in logs. Reference them as ${{ secrets.SECRET_NAME }}. For environment-specific secrets, GitHub's Environments feature lets you require manual approval before a deployment proceeds, and scopes secrets to specific environments (staging, production).
GitHub Actions fits naturally into the DevOps tools stack for teams already using GitHub. For a comparison with self-hosted CI, see Jenkins.
