GitHub Actions for Node
Configure GitHub Actions for Node.js 24 TypeScript services: matrix builds, npm cache, artifacts, and OIDC deploy.
Search across all documentation pages
Configure GitHub Actions for Node.js 24 TypeScript services: matrix builds, npm cache, artifacts, and OIDC deploy.
Quick-reference recipe card - copy-paste ready.
name: CI
on: [pull_request, push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run lint && npm run typecheck && npm run testWhen to reach for this: Every Node repo on GitHub. This is the default CI skeleton before Docker and deploy jobs.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
cache-dependency-path: package-lock.json
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Typecheck
run: npm run typecheck
- name: Test with coverage
run: npm run test -- --coverage
- name: Upload coverage
if: matrix.node-version == 24
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
retention-days: 7
docker:
needs: quality
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/acme/api:${{ github.sha }}
ghcr.io/acme/api:main
cache-from: type=gha
cache-to: type=gha,mode=maxWhat this demonstrates:
cache: npm keyed on lockfile via setup-nodeconcurrency cancels superseded PR runsmain push after quality job passesactions/setup-node@v4 with cache: npm hashes package-lock.json. For monorepos:
cache-dependency-path: |
package-lock.json
services/api/package-lock.json| Feature | Use for |
|---|---|
actions/cache | node_modules, Docker layers |
actions/upload-artifact | Coverage reports, built dist/, Lambda zips |
- uses: actions/upload-artifact@v4
with:
name: lambda-zip
path: function.zipDownstream deploy job downloads the same zip tested in CI.
permissions:
id-token: write
contents: read
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: us-east-1Trust policy on IAM role limits to repo:acme/api:ref:refs/heads/main.
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 3s
--health-retries 5
steps:
- run: npm run test:integration
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgresnpm install instead of npm ci - non-reproducible CI. Fix: commit lockfile; always npm ci.fail-fast: false - Node 22 failure hides Node 24 result. Fix: set fail-fast: false when comparing versions.node_modules manually - often slower than setup-node cache. Fix: use built-in npm cache unless monorepo tooling requires custom paths.:latest deploy tags only - cannot rollback. Fix: always push ${{ github.sha }} tag.concurrency - queued PR pushes waste minutes. Fix: cancel in-progress runs on same branch.paths: ['Dockerfile', 'src/**'] or build only on main.| Alternative | Use When | Don't Use When |
|---|---|---|
| GitHub Actions | Repo on GitHub | GitLab-only org (use GitLab CI) |
| CircleCI / Buildkite | Custom runners, large monorepos | Simple single-service repos |
| Nx Cloud / Turborepo | Affected task graph | Tiny single-package apps |
| Self-hosted runners | GPU or VPC-internal tests | Default OSS public repos |
Matrix 22 and 24 if you support both Active LTS lines. Drop 20 when officially sunset for your product.
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
cache: pnpmUnder 10 minutes for unit stages. Split slow integration tests into a nightly workflow if needed.
Possible but separate workflows improve clarity: ci.yml on PR, release.yml on tag. See CI/CD Basics.
Use npx playwright install --with-deps step and shard tests across matrix jobs for large suites.
Enable GitHub secret scanning and gitleaks action on PRs. Block commits with .env contents.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026