You push code, tests run automatically, and your app deploys itself. That's CI/CD, and GitHub Actions makes it free and surprisingly easy.
What Is CI/CD?
- CI (Continuous Integration): Automatically run tests when you push code
- CD (Continuous Deployment): Automatically deploy when tests pass
GitHub Actions gives you 2,000 free minutes/month on public repos (and 500 on private).
Your First Workflow: Run Tests on Every Push
Create .github/workflows/test.yml:
name: Run Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup 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
- name: Run linter
run: npm run lint
That's it. Push this file and GitHub will run your tests on every push and PR.
Deploy to Vercel on Merge
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Deploy a Docker App to Railway
name: Deploy to Railway
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
deploy:
needs: test # Only deploy if tests pass
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Railway CLI
run: npm install -g @railway/cli
- name: Deploy
run: railway up
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
Useful Workflow Patterns
Matrix Testing (Multiple Node Versions)
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
Cache Dependencies
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
Run Only When Specific Files Change
on:
push:
paths:
- 'src/**'
- 'package.json'
Scheduled Workflows (Cron)
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9 AM UTC
Setting Up Secrets
- Go to your repo > Settings > Secrets and variables > Actions
- Click "New repository secret"
- Add your secret (e.g.,
VERCEL_TOKEN)
Never put secrets directly in workflow files.
Common Mistakes
-
Not using
npm ci— Usenpm ciinstead ofnpm installin CI (it's faster and more deterministic) -
Forgetting to cache — Caching
node_modulesor~/.npmsaves 30-60 seconds per run -
Running everything on
push— Use path filters to avoid running tests when you only changed docs -
Not setting timeout — Add
timeout-minutes: 10to prevent stuck workflows from eating your minutes
My Recommended Starter Setup
For most projects, you need just two workflows:
- test.yml — Runs on every push/PR: lint + test
- deploy.yml — Runs on push to main (after tests pass): deploy to production
Start simple, add complexity only when you need it.
More developer guides at lucasmdevdev.github.io
Top comments (0)