Here's a common scenario: you push code to GitHub, then manually run tests on your local machine, then manually deploy to your server. That's tedious. What if all that happened automatically?
That's exactly what GitHub Actions does.
What's CI/CD?
Before we dive in, let's get the jargon straight:
- CI (Continuous Integration) - automatically run tests every time you push code
- CD (Continuous Deployment) - automatically deploy when tests pass
Together, they mean: push code → tests run → if it passes, it deploys. No human involvement required.
Your First Workflow
GitHub Actions lives in your repo. You create a workflow file and GitHub runs it when triggered.
Create a file at .github/workflows/test.yml in your repo:
name: Run Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Let's break this down:
- on - when to run this (push or pull request)
- jobs - what to run (we call it "test")
- runs-on - what machine (ubuntu-latest)
- steps - individual actions to take
Now whenever you push, GitHub spins up a fresh Ubuntu machine, checks out your code, installs Node, and runs your tests. For free. (Up to a certain limit.)
Deploying When Tests Pass
Okay, tests pass. Now let's deploy—but only if everything worked:
jobs:
test:
runs-on: ubuntu-latest
steps:
# ... run tests ...
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to server
run: |
echo "Deploying to production..."
# Add your actual deploy commands here
The magic is needs: test. This tells GitHub: "don't run the deploy job until the test job completes successfully."
Also useful: if: github.ref == 'refs/heads/main' means "only deploy when pushing to main branch."
Common Things People Do
- Run test suites automatically
- Lint code (check for style errors)
- Build Docker images
- Deploy to cloud providers (AWS, Heroku, etc.)
- Send Slack notifications when deployments happen
- Run scheduled jobs (nightly builds, weekly reports)
Testing Multiple Versions
Want to make sure your code works on Node 16, 18, and 20? Matrix testing runs your tests on all versions at once:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16, 18, 20, 22]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
This spawns four parallel test jobs—one for each Node version. If any fail, you know immediately.
Is It Worth It?
Short answer: yes. Even for small projects.
Once set up, you never forget to run tests before deploying. You catch bugs before they reach production. And it's free for public repos (and has a generous free tier for private).
Start simple. Just run your tests on every push. Then add deployment later.