GitHub Actions CI/CD in 2026: A Practical Beginner's Guide
GitHub Actions has become the de facto CI/CD platform for developers in 2026. With 100 million repositories using it and over 10,000 community-maintained actions in the GitHub Marketplace, it's the easiest way to automate builds, tests, and deployments directly from your GitHub repository. This guide covers everything you need to go from zero to production deployment in under an hour.
What GitHub Actions Actually Does
GitHub Actions is an event-driven automation platform. You define triggers (like a push to main, a pull request, or a scheduled cron), and GitHub runs a workflow in response. Each workflow is a collection of jobs, each job is a collection of steps, and each step runs either a shell command or an action.
Think of it as programmable infrastructure: instead of manually running build commands on a server, you describe what should happen, and GitHub handles the execution on ephemeral virtual machines.
The Basic Workflow File
Workflows live in .github/workflows/ in your repository. Here's a minimal example that runs tests on every push:
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: '20'
- run: npm ci
- run: npm test
This five-line workflow (plus config) is a complete CI pipeline. On every push or PR to main, GitHub spins up a fresh Ubuntu machine, checks out your code, sets up Node.js 20, installs dependencies, and runs your tests. If any step fails, the workflow fails and you get a red X on your commit.
Building a Real-World Deployment Pipeline
Let's build something useful: a pipeline that runs tests, builds a static site, and deploys to Cloudflare Pages.
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-blog
directory: dist
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
This pipeline has two jobs: test runs first, and deploy only runs if test succeeds. The needs: test directive enforces this ordering. The secrets.CLOUDFLARE_API_TOKEN is a secret stored in your repository settings — never commit API keys to your workflow file.
Managing Secrets
Secrets in GitHub Actions are encrypted environment variables you configure at the repository or organization level. To add a secret:
- Go to your repository → Settings → Secrets and variables → Actions
- Click "New repository secret"
- Add CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, or any other keys
In your workflow file, reference secrets as ${{ secrets.SECRET_NAME }}. They're masked in logs — if someone prints a secret by accident, GitHub replaces it with ***.
Caching Dependencies for Faster Builds
The slowest part of most CI pipelines is installing dependencies. GitHub Actions supports caching to speed this up. For npm, the actions/setup-node@v4 action has built-in caching:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # caches node_modules based on package-lock.json
For other package managers, use the generic actions/cache action. A typical Python cache looks like:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
Matrix Builds: Test Across Multiple Versions
Want to test your code against Node 18, 20, and 22 simultaneously? Use a matrix strategy:
jobs:
test:
strategy:
matrix:
node-version: ['18', '20', '22']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
GitHub runs three parallel jobs — one for each Node version. If one fails, you see exactly which version broke.
Scheduled Workflows
GitHub Actions supports cron syntax for scheduled runs. To run a job every day at 2am UTC:
on:
schedule:
- cron: '0 2 * * *' # 2:00 AM UTC = 7:00 AM PDT
Useful for: daily security scans, weekly dependency update checks, regular database backups, or sending digest emails.
Reusable Workflows
If you have the same CI process across 10 repositories, reusable workflows prevent copy-paste. Create a workflow in .github/workflows/reusable.yml:
# .github/workflows/reusable.yml
on:
workflow_call:
inputs:
node-version:
required: true
type: string
secrets:
CLOUDFLARE_TOKEN:
required: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build
- uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-site
directory: dist
Then call it from any repository:
uses: ./.github/workflows/reusable.yml
with:
node-version: '20'
secrets:
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Free Tier Limits
GitHub's free tier includes 2,000 minutes/month of Actions runtime for private repositories (2,000 minutes for public repos). For most personal projects and small teams, this is plenty. The key tips to stay under the limit:
- Cache dependencies aggressively
- Skip CI for docs-only changes with path filters
- Use
workflow_dispatchto manually trigger expensive jobs only when needed
AFFILIATE NOTE: GitHub Copilot is available as a standalone subscription for $10/month or $100/year for individual developers. GitHub Teams is $4/user/month and includes Copilot for Business. Team plans also get increased Actions minutes (3,000 minutes/month).