How to Set Up a CI/CD Pipeline with GitHub Actions for a Node.js App

How to Set Up a CI/CD Pipeline with GitHub Actions for a Node.js App

by | Jun 30, 2026 | Uncategorized | 0 comments

Why GitHub Actions CI/CD for Node.js?

If you are building a Node.js application in 2026, manual deployments should be a thing of the past. A well-configured CI/CD pipeline catches bugs early, enforces code quality, and ships features faster. GitHub Actions is one of the most popular tools to achieve this because it lives right inside your repository, requires no external services, and offers a generous free tier.

In this guide, we will walk through every step of configuring a GitHub Actions CI/CD pipeline for a Node.js app. By the end you will have a workflow that automatically lints your code, runs your tests, builds the project, and deploys it to a cloud provider every time you push to the main branch.

What You Will Need

  • A GitHub account and a repository containing your Node.js project
  • A package.json with scripts for lint, test, and build
  • A cloud provider account (we will use AWS in this tutorial, but the pattern applies to Azure, GCP, Render, Railway, or any provider)
  • Basic familiarity with YAML syntax
software deployment pipeline automation

Step 1: Prepare Your Node.js Project

Before touching GitHub Actions, make sure your project has the right npm scripts. Open your package.json and confirm you have entries similar to these:

{
  "scripts": {
    "lint": "eslint . --ext .js,.ts",
    "test": "jest --coverage",
    "build": "tsc" 
  }
}

Adjust the commands to match your stack. If you use Vitest instead of Jest, or Biome instead of ESLint, swap them in. The key point is that each stage of your pipeline maps to an npm script.

Step 2: Create the GitHub Actions Workflow File

GitHub Actions workflows live inside a special directory in your repository:

.github/workflows/

Create a new file at .github/workflows/ci-cd.yml. This single file will define the entire pipeline.

Step 3: Define the Trigger Events

At the top of the YAML file, specify when the workflow should run. A common pattern is to trigger on pushes and pull requests to the main branch:

name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

This means every commit pushed to main and every pull request targeting main will kick off the pipeline automatically.

software deployment pipeline automation

Step 4: Add the CI Job (Lint, Test, Build)

Now we define the first job. This job installs dependencies, lints the code, runs the test suite, and builds the application.

jobs:
  ci:
    name: Lint, Test and Build
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18, 20, 22]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build project
        run: npm run build

What Each Step Does

Step Purpose
Checkout code Clones your repository into the runner so subsequent steps can access the source code.
Set up Node.js Installs the desired Node.js version and enables npm caching to speed up future runs.
Install dependencies npm ci performs a clean install based on the lock file, ensuring reproducible builds.
Run linter Catches code style issues and potential errors before they reach production.
Run tests Executes your unit and integration tests. If any test fails, the pipeline stops.
Build project Compiles TypeScript, bundles assets, or performs whatever your build step requires.

Why Use a Node Version Matrix?

The strategy.matrix block runs your CI job against multiple Node.js versions in parallel. This is incredibly useful when you maintain a library or when your production environment might differ from your development machine. If you only target one version, simply remove the matrix and hardcode the version number.

Step 5: Add the CD Job (Deploy to AWS)

The deployment job should only run after the CI job succeeds and only on pushes to the main branch (not on pull requests). Here is an example that deploys to AWS Elastic Beanstalk, but you can adapt the deployment step for any cloud provider.

  deploy:
    name: Deploy to AWS
    runs-on: ubuntu-latest
    needs: ci
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

      - name: Deploy to Elastic Beanstalk
        uses: einaregilsson/beanstalk-deploy@v22
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: my-node-app
          environment_name: my-node-app-production
          version_label: ${{ github.sha }}
          region: eu-west-1
          deployment_package: deploy.zip

Key Points About the Deploy Job

  • needs: ci ensures this job waits for the CI job to pass.
  • The if condition prevents deployments from running on pull requests.
  • Secrets like AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are stored securely in your repository settings (more on that in the next step).

Step 6: Configure Repository Secrets

Never hardcode credentials in your workflow file. GitHub provides encrypted secrets for this purpose.

  1. Go to your repository on GitHub.
  2. Navigate to Settings > Secrets and variables > Actions.
  3. Click New repository secret.
  4. Add your cloud provider credentials (for example, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY).

These secrets are masked in logs and are only available to workflows running in your repository.

software deployment pipeline automation

Step 7: Verify the Pipeline

Push your changes to the main branch and head over to the Actions tab in your repository. You should see the workflow kick off immediately. The CI job will run first across your Node.js version matrix, and once it passes, the deploy job will start.

If anything fails, click on the failed step to see the full log output. Most common issues at this stage include:

  • Missing npm scripts in package.json
  • Incorrect secret names or values
  • Mismatched indentation in the YAML file

The Complete Workflow File

Here is the full .github/workflows/ci-cd.yml for reference:

name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    name: Lint, Test and Build
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18, 20, 22]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build project
        run: npm run build

  deploy:
    name: Deploy to AWS
    runs-on: ubuntu-latest
    needs: ci
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

      - name: Deploy to Elastic Beanstalk
        uses: einaregilsson/beanstalk-deploy@v22
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: my-node-app
          environment_name: my-node-app-production
          version_label: ${{ github.sha }}
          region: eu-west-1
          deployment_package: deploy.zip

Adapting the Pipeline for Other Cloud Providers

The CI portion of the pipeline stays exactly the same regardless of where you deploy. Only the deploy job changes. Here is a quick comparison of popular targets:

Cloud Provider Recommended Action / Method Required Secrets
AWS Elastic Beanstalk einaregilsson/beanstalk-deploy AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
Azure App Service azure/webapps-deploy AZURE_WEBAPP_PUBLISH_PROFILE
Google Cloud Run google-github-actions/deploy-cloudrun GCP_SA_KEY or Workload Identity
Render Deploy Hook (simple curl command) RENDER_DEPLOY_HOOK_URL
Railway Railway CLI or GitHub integration RAILWAY_TOKEN
Vercel amondnet/vercel-action VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID
software deployment pipeline automation

Tips to Optimize Your GitHub Actions CI/CD Pipeline

Once your basic pipeline is running, consider these improvements to make it faster and more reliable:

1. Cache Node Modules Aggressively

The actions/setup-node action supports built-in caching via the cache input. This alone can cut your install step from minutes to seconds on subsequent runs.

2. Use Concurrency Controls

If multiple pushes happen in quick succession, you probably only care about the latest one. Add a concurrency group to cancel in-progress runs:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

3. Add Status Badges

Show the world your build is passing. Add this Markdown snippet to your README:

![CI/CD](https://github.com/YOUR_USER/YOUR_REPO/actions/workflows/ci-cd.yml/badge.svg)

4. Use Environment Protection Rules

For production deployments, configure GitHub Environments with required reviewers. This adds a manual approval gate before the deploy job executes.

5. Run Security Audits

Add npm audit as an extra step in your CI job to catch known vulnerabilities in your dependencies before they reach production.

Common Mistakes to Avoid

  • Using npm install instead of npm ci in CI environments. npm ci is faster and more predictable because it relies on the lock file.
  • Forgetting to set needs on the deploy job. Without it, both jobs run in parallel and deployment might happen before tests pass.
  • Storing secrets in the workflow YAML. Always use GitHub encrypted secrets.
  • Not restricting deploy to the main branch. Without the if condition, every pull request could trigger a production deployment.
  • Ignoring workflow run times. GitHub Actions minutes are free for public repos but limited for private ones. Monitor usage and optimize caching.

Frequently Asked Questions

Is GitHub Actions free for CI/CD?

GitHub Actions is free for public repositories with essentially unlimited minutes. For private repositories, free plans include 2,000 minutes per month. Paid plans offer more. Check the GitHub pricing page for current limits.

Can I use GitHub Actions CI/CD with a Node.js monorepo?

Yes. You can use path filters in the on trigger to run workflows only when files in specific directories change. Combined with tools like Nx or Turborepo, this makes monorepo CI very efficient.

How do I deploy a Node.js Docker container with GitHub Actions?

Add steps to build your Docker image and push it to a container registry (Docker Hub, GitHub Container Registry, Amazon ECR). Then trigger a deployment on your container orchestration platform. The CI job (lint, test, build) remains exactly the same.

What Node.js versions should I test against in 2026?

As of April 2026, Node.js 22 is the current LTS release and Node.js 24 is the active release. We recommend testing against at least Node.js 20 and 22 to cover the most common production environments.

Can I run GitHub Actions on my own servers?

Yes. GitHub offers self-hosted runners that you install on your own infrastructure. This is useful for security-sensitive projects or when you need specialized hardware.

How long does a typical Node.js CI/CD pipeline take?

A well-optimized pipeline with cached dependencies usually completes in 1 to 3 minutes for the CI job. Deployment time varies by provider but typically adds another 1 to 5 minutes.

Wrapping Up

Setting up a GitHub Actions CI/CD pipeline for a Node.js application is one of the highest-value tasks you can invest time in as a developer. Once configured, it runs automatically on every push, protects your main branch from broken code, and deploys tested builds to production without any manual intervention.

The workflow we built in this guide covers linting, testing across multiple Node.js versions, building, and deploying to AWS. You can adapt the deploy step for virtually any cloud provider using the comparison table above.

If you need help setting up CI/CD pipelines or automating your development workflow, Box Software can help. We specialize in building robust, maintainable software delivery processes that let your team focus on writing great code.