GitHub Actions: Complete CI/CD Guide for Any Project
Set up GitHub Actions CI/CD from scratch — workflows, jobs, caching, secrets, matrix builds, deployment, and reusable patterns for any language or framework.
What you'll learn
- ✓How GitHub Actions workflows, jobs, and steps work
- ✓Setting up CI pipelines for testing and linting
- ✓Deploying to production on push or tag
- ✓Caching dependencies for faster builds
- ✓Using secrets and environment variables safely
Prerequisites
- •A GitHub repository
- •Basic command-line familiarity
GitHub Actions runs your CI/CD pipelines directly inside GitHub. No external service, no extra account. You write YAML files that describe when to run, what to run, and where to run it. This guide covers everything from your first workflow to production deployment patterns.
How GitHub Actions Works
Every workflow lives in .github/workflows/ as a YAML file. A workflow contains one or more jobs, each job runs on a virtual machine (the runner), and each job contains steps that execute commands or use pre-built actions.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm test
This workflow triggers on pushes and pull requests to main. It checks out the code, installs Node.js, installs dependencies, and runs tests.
Trigger Events
The on key controls when workflows run. The most common triggers:
on:
push:
branches: [main, develop]
paths:
- 'src/**'
- 'package.json'
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
inputs:
environment:
description: 'Deploy target'
required: true
default: 'staging'
type: choice
options:
- staging
- production
workflow_dispatch lets you trigger workflows manually from the GitHub UI with custom inputs. schedule uses cron syntax for recurring runs.
Jobs and Dependencies
Jobs run in parallel by default. Use needs to create dependencies:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
deploy:
needs: [lint, test]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- run: npm run deploy
lint and test run in parallel. deploy waits for both to pass and only runs on the main branch.
Caching Dependencies
Without caching, every run downloads and installs all dependencies from scratch. The actions/cache action stores files between runs:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
The setup actions for Node, Python, Go, and others have built-in caching. For custom caching:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
The key includes a hash of the lock file so the cache invalidates when dependencies change.
Secrets and Environment Variables
Store sensitive values in repository or organization secrets (Settings → Secrets and variables → Actions):
steps:
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: aws s3 sync ./dist s3://my-bucket
Secrets are masked in logs automatically. For non-sensitive configuration, use vars:
env:
API_URL: ${{ vars.API_URL }}
Matrix Builds
Test across multiple versions, operating systems, or configurations:
jobs:
test:
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
This creates 9 jobs (3 Node versions × 3 operating systems). fail-fast: false lets all combinations finish even if one fails.
Artifacts
Share files between jobs or download build outputs:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: ls -la dist/
Environments and Approvals
GitHub Environments add protection rules like required reviewers and wait timers:
jobs:
deploy-staging:
environment: staging
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to staging"
deploy-production:
needs: deploy-staging
environment: production
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to production"
Configure the production environment in repository settings to require manual approval before the job runs.
Docker Builds
Build and push Docker images as part of your pipeline:
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
The cache-from and cache-to options use GitHub’s cache backend to speed up Docker builds.
Reusable Workflows
Extract common patterns into reusable workflows that other repositories can call:
# .github/workflows/reusable-deploy.yml
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
deploy-key:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
env:
DEPLOY_KEY: ${{ secrets.deploy-key }}
Call it from another workflow:
jobs:
deploy:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
secrets:
deploy-key: ${{ secrets.DEPLOY_KEY }}
Conditional Steps
Control which steps run based on conditions:
steps:
- name: Run only on main
if: github.ref == 'refs/heads/main'
run: echo "On main branch"
- name: Run only on PR
if: github.event_name == 'pull_request'
run: echo "This is a PR"
- name: Run on failure
if: failure()
run: echo "Something failed"
- name: Always run
if: always()
run: echo "Cleanup"
Concurrency Control
Prevent multiple deployments from running simultaneously:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
This cancels any in-progress run for the same branch when a new push arrives. Essential for deployment workflows where only the latest commit matters.
A Complete Production Workflow
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test -- --coverage
- uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: build
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: echo "Deploy dist/ to production"
Wrapping Up
GitHub Actions gives you CI/CD without leaving GitHub. Start with a simple test workflow, add caching to speed it up, use matrix builds for coverage, and layer in deployment jobs with environment protection. The YAML is declarative, the marketplace has thousands of pre-built actions, and the free tier is generous enough for most open-source and small-team projects.
Related articles
- CI/CD GitHub Actions vs Jenkins: CI/CD Compared
Compare GitHub Actions and Jenkins for CI/CD pipelines. Covers setup, configuration, plugins, pricing, and when to choose each tool for your team.
- CI/CD CI/CD Pipeline Best Practices for Faster Deployments
Build fast, reliable CI/CD pipelines — parallel stages, caching, fast feedback, trunk-based development, and deployment automation patterns.
- CI/CD GitHub Actions Reusable Workflows Tutorial
Stop copy-pasting CI YAML across repos. Learn how to build reusable GitHub Actions workflows with inputs, secrets, outputs, and per-environment overrides.
- CI/CD CI/CD Artifact Management: Build, Store, and Promote
Learn how to manage build artifacts in CI/CD pipelines including versioning, storage, promotion strategies, and cleanup policies.