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.

Frequently Asked Questions