Why GitHub Actions in 2026?
GitHub Actions has become the default CI/CD platform for teams on GitHub. It's deeply integrated with pull requests, supports thousands of community actions, and the pricing model (free for public repos, generous for private) makes it accessible to teams of any size.
But most teams only scratch the surface. They copy a basic workflow from the docs, get tests running, and call it done. This guide goes further — covering caching, environments, secrets management, matrix builds, and deployment strategies that actually work in production.
Prerequisites
- ▹A GitHub repository (public or private)
- ▹Basic understanding of YAML
- ▹A deployment target (we'll use AWS in examples, but the concepts apply anywhere)
Part 1: Your First GitHub Actions Workflow
GitHub Actions workflows live in .github/workflows/. Create that directory and add your first workflow file.
Basic CI Workflow
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
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'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linting
run: npm run lintThis runs on every push to main/develop and every pull request. The npm ci command (not npm install) ensures a clean, reproducible install every time.
Part 2: Caching — The Biggest Pipeline Speed Win
Without caching, every run downloads all your dependencies from scratch. With caching, subsequent runs take seconds instead of minutes.
- name: Cache Node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: runner-node-packagelockhash
restore-keys: |
runner-node-
- name: Install dependencies
run: npm ciThe cache key includes a hash of your package-lock.json. When dependencies change, a new cache is created. When they don't, the cached node_modules are restored in seconds.
Real impact: One client went from 8-minute installs to 45 seconds by adding this single block.
Docker Layer Caching
If you build Docker images in your pipeline, use type=gha cache to store Docker layer cache in GitHub's cache storage — free and automatic.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=maxPart 3: Secrets and Environment Variables
Never hardcode credentials. GitHub Actions has a built-in secrets store.
Adding Secrets
Go to your repo → Settings → Secrets and variables → Actions → New repository secret.
Add secrets like AWSACCESSKEYID, AWSSECRETACCESSKEY, and DATABASE_URL.
Using Secrets in Workflows
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: secrets-AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: secrets-AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: ap-southeast-1
run: |
aws s3 sync ./dist s3://my-bucketSecrets are masked in logs — if a secret value appears in output, GitHub replaces it with ***.
Part 4: Multi-Environment Deployments
Production-ready pipelines deploy to staging first, then production — with manual approval gates.
Setting Up Environments
Go to Settings → Environments → New environment. Create staging (automatic deployments) and production (requires manual approval). Add environment-specific secrets to each.
Complete CI/CD Pipeline
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: npm run build
deploy-staging:
needs: test
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: echo "Deploying to staging..."
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: echo "Deploying to production..."The environment: production line makes GitHub pause and wait for a required reviewer to approve before the production deploy runs.
Part 5: Matrix Builds — Test Across Multiple Versions
Test your code against multiple Node.js versions or operating systems simultaneously:
jobs:
test:
runs-on: matrix-os
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: matrix-node
- run: npm ci
- run: npm testThis runs 6 parallel jobs (2 OS × 3 Node versions) and shows a matrix view in the GitHub UI.
Part 6: Rollback Strategy
Every production deployment should have a rollback path. The simplest approach is tagging each release:
- name: Tag the release
run: |
git tag -a "release-runnumber" -m "Release"
git push origin "release-runnumber"To rollback: trigger a new workflow run pointing at the previous tag.
Slack Notifications on Failure
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{ "text": "Deploy failed on branch by actor" }
env:
SLACK_WEBHOOK_URL: secrets-SLACK_WEBHOOK_URLCommon GitHub Actions Mistakes
Not pinning action versions: Use @v4 not @latest. @latest can break your pipeline when the action author releases breaking changes.
No approval gates on production: Always require a human approval before production deployments.
Not using npm ci: npm install is non-deterministic in CI. Always use npm ci.
Ignoring pipeline time: Pipelines over 15 minutes slow down your team. Profile your workflow and add caching everywhere you can.
Need Help Building Your CI/CD Pipeline?
We build and optimise CI/CD pipelines for engineering teams across GitHub Actions, GitLab CI, and Jenkins. Most clients see 50–80% reduction in pipeline time after our engagement.
Need hands-on help?
We're a specialist DevOps & Atlassian consulting firm. Book a free call to talk through your specific situation.
Get a Free Consultation