← Writing

GitHub Action - Level 1

Github Actions is a CI/CD tool built into GitHub.

Terminologies

  1. Workflow
  2. Event
  3. Job
  4. Runner
  5. Step

GitHub Actions or workflows should be at /.github/workflows . It is compulsory.

Example Workflow

name: Example CI

on:
	pull_request: "main"
	
jobs:
	test_runner: 
		runs-on: ubuntu-latest
		
		steps:
			- name: Checkout code
				uses: actions/checkout@v4
				
			- name: Setup Node
				uses: actions/setup-node@v4
				with:
					node-version: 18
			
			- name: Install dependencies
				run: npm install
				
			- name: Run tests
				run: npm test

Explaination:

The above workflow runs on pull request to main branch. The name for this workflow is Example CI . It only runs one job viz. test_runner . In the job all the mentioned steps execute one after another.

In Step 1: The repo is clone to the VM on github server.

In Step 2: Node is installed on that VM.

In Step 3: All dependencies are installed.

In Step 4: tests are run.

Common Questions:

  1. test_runner is just a job name. We can name it anything. But some conventional names are build, test, deploy, lint, ci and cd.

  2. We can create multiple workflows in .github/workflows and can name the files anything. The visible name in GitHub UI is the name: Example CI for above example. Also, GitHub automatically detects all .yml files in that folder and treats each as separate workflow and run them independently. Each workflow has its own trigger.

  3. What is action/setup-node@v4 ?

    action/setup-node@v4 , actions/checkout#v4 are prebuilt actions from github.

    Some common prebuilt actions and there use cases are

    action use case
    actions/checkout@v4 Clones your repo into to runner
    actions/setup-node@v4 Install Node.js in the runner
    actions/setup-python@v4 install python in the runner
    appleboy/ssh-action Connect to remote server via SSH
    appleboy/scp-action used to copy folder to remote server
    actions/upload-artifact@v4 Upload build artifact
    actions/download-artifact@v4 download build artifacts
  4. Some most common triggers are push, pull_request, schedule.

Some More Examples

1. Basic Node.js application CD

name: Deploy Node App

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

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

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build

      - name: Deploy to EC2
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            cd /var/www/my-app
            git pull origin main
            npm install
            npm run build
            pm2 restart my-app

EC2_HOST , EC2_SSH_KEY are need to specify in GitHub Secrets. Repo → Settings → Secrets → Actions → Add Secrets

2. Node.js Optimized CD to use built or dist

name: Deploy to EC2

on:
  push:
    branches:
      - main

jobs:
  deploy:
    name: Deploy Backend
    runs-on: ubuntu-latest

    steps:
		    # Checkout Code
      - name: Checkout code
        uses: actions/checkout@v4
        
				# Setup Node
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          
				# Install Dependencies
      - name: Install deps
        run: npm install
        
				# Build project
      - name: Build project
        run: npm run build

				# Debugging files: (Can be removed)
      - name: Debug workspace
        run: |
          echo "Current path:"
          pwd
          echo "Files in root:"
          ls -la
          echo "Looking for dist:"
          ls -la dist || echo "DIST NOT FOUND"

	      # Upload compiled build to EC2
      - name: Upload build to EC2
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          source: "dist/*, package.json"
          target: "/home/ubuntu/server/test-repo"

				# Install production deps & restart app
      - name: Restart backend
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          script: |
            set -e
            cd /home/ubuntu/server/careConnect-server

            echo "Installing production dependencies..."
            npm install --omit=dev --no-audit --no-fund

            echo "Restarting backend..."
            pm2 delete test_server || true
            pm2 start dist/main.js --name test_server

            pm2 save

3. Simple CI + CD for nodejs Application

name: Node CI + CD

on:
  push:
    branches:
      - main
      - develop
  pull_request:

jobs:
	  # BUILD & TEST JOB
  build:
    runs-on: ubuntu-latest

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

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test

      - name: Build project
        run: npm run build

      # Save build as artifact (optional but good practice)
      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-files
          path: |
            dist
            package.json
            package-lock.json

  # DEPLOY JOB
  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: build-files

      - name: Copy files to EC2
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          source: "dist,package.json,package-lock.json"
          target: "/var/www/node-app"

      - name: Restart app on EC2
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            cd /var/www/node-app
            npm ci --omit=dev
            pm2 reload node-app

This workflow performs:

  1. CI:
    1. Runs on push (main, develop) and pull requests
    2. Installs dependencies
    3. Runs tests
    4. Builds the project
    5. Uploads build artifacts
  2. CD:
    1. Runs only after successful build job
    2. Deploys only when branch is 'main’
    3. Downloads build artifacts
    4. Copies dist + package files to EC2
    5. Installs production dependencies
    6. Restarts application using PM2

4. Optimised CI + CD for Node.js Application

name: NestJS CI

on:
  pull_request:
  push:
    branches:
      - develop
      - main

jobs:
  test:
    runs-on: ubuntu-latest

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

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run lint
        run: npm run lint

      - name: Run tests
        run: npm run test

Runs on:

  • PR
  • push to develop
  • push to main

Executes:

  • Lint
  • Unit tests

If anything fails → workflow fails.

name: NestJS Deploy

on:
  workflow_run:
    workflows: ["NestJS CI"]
    types:
      - completed

jobs:
  deploy:
    if: >
      github.event.workflow_run.conclusion == 'success' &&
      github.event.workflow_run.head_branch == 'main'

    runs-on: ubuntu-latest

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

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 18

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

      - name: Copy build files to EC2
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          source: "dist,package.json,package-lock.json"
          target: "/var/www/nest-app"

      - name: Restart app on EC2
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            cd /var/www/nest-app
            npm ci --omit=dev
            pm2 reload nest-app

Related Blog