Back to Blog
DevOps7 min read

CI/CD Pipeline with GitHub Actions: Automated Deployment

Master the art of building a robust CI/CD pipeline using GitHub Actions for automated deployments. Learn practical strategies and real-world examples to streamline your software delivery process.

Jay Salot

Jay Salot

Sr. Full Stack Developer

March 19, 2026 · 7 min read

Share
Developer working on laptop

In today's fast-paced software development landscape, a robust CI/CD (Continuous Integration/Continuous Delivery) pipeline is no longer a luxury, but a necessity. GitHub Actions provides a powerful and flexible platform to automate your build, test, and deployment processes directly within your GitHub repository. This blog post will guide you through building a complete CI/CD pipeline using GitHub Actions for automated deployment, sharing practical tips and real-world examples along the way.

What is CI/CD?

Before diving into the specifics of GitHub Actions, let's define CI/CD and its importance.

Continuous Integration (CI)

Continuous Integration is a development practice where developers frequently integrate code changes into a central repository. Each integration is then verified by an automated build and test process. This helps detect integration errors early and allows for faster feedback.

Continuous Delivery and Continuous Deployment

Continuous Delivery extends CI by automating the release process. The code is automatically built, tested, and prepared for release to production. Continuous Deployment takes this one step further by automatically deploying every change that passes the automated tests to production. The difference lies in whether a human gatekeeper is required before the final deployment.

GitHub Actions: The Basics

GitHub Actions is a workflow automation platform directly integrated into GitHub. It allows you to automate tasks within your repository, such as building, testing, and deploying code. Actions are defined in YAML files and are triggered by events, such as pushes, pull requests, or scheduled events.

Key Concepts

  • Workflows: Automated processes defined in YAML files.
  • Events: Triggers that initiate a workflow (e.g., push, pull request).
  • Jobs: Sets of steps that run on the same runner.
  • Steps: Individual tasks within a job (e.g., running a script, executing a command).
  • Runners: Servers that execute the jobs. GitHub provides hosted runners (Linux, Windows, macOS), or you can use self-hosted runners.
  • Actions: Reusable units of code that perform specific tasks. You can use pre-built actions from the GitHub Marketplace or create your own.

Building a Basic CI Pipeline

Let's start with a simple CI pipeline that builds and tests a Node.js application. We'll create a .github/workflows/ci.yml file in our repository.

name: Node.js CI

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

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x, 18.x, 20.x]

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - name: Install dependencies
      run: npm ci
    - name: Run tests
      run: npm test

Explanation:

  • name: Node.js CI: Defines the name of the workflow.
  • on:: Specifies the events that trigger the workflow. In this case, it's triggered by pushes and pull requests to the main branch.
  • jobs:: Defines the jobs to be executed. Here, we have a single job named build.
  • runs-on: ubuntu-latest: Specifies the runner to use (Ubuntu in this case).
  • strategy: matrix: Defines a matrix of Node.js versions to test against.
  • steps:: Defines the steps to be executed within the job.
  • actions/checkout@v3: Checks out the repository code.
  • actions/setup-node@v3: Sets up the specified Node.js version.
  • npm ci: Installs dependencies using npm ci (clean install).
  • npm test: Runs the tests.

Adding Automated Deployment

Now, let's extend our CI pipeline to include automated deployment to a staging environment. We'll assume we have a server accessible via SSH.

name: Node.js CI/CD

on:
  push:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '16.x'
        cache: 'npm'
    - name: Install dependencies
      run: npm ci
    - name: Run tests
      run: npm test

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to Staging
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.STAGING_USER }}
          key: ${{ secrets.STAGING_KEY }}
          port: 22
          script: |
            cd /var/www/staging
            git pull origin main
            npm ci
            npm run build
            pm2 restart my-app

Explanation:

  • needs: build: Ensures that the deploy job runs only after the build job has completed successfully.
  • appleboy/ssh-action@master: Uses the ssh-action to execute commands on the staging server.
  • secrets.STAGING_HOST, secrets.STAGING_USER, secrets.STAGING_KEY: These are GitHub Secrets that store sensitive information (host, username, SSH key) securely. You need to configure these secrets in your repository settings.
  • script:: Contains the commands to be executed on the staging server. This includes pulling the latest code, installing dependencies, building the application, and restarting the PM2 process.

Important: Always store sensitive information (passwords, API keys, SSH keys) as GitHub Secrets. Never hardcode them in your workflow files.

Advanced Pipeline Strategies

Here are some advanced strategies to enhance your CI/CD pipeline:

Environment Variables

Use environment variables to configure your application differently for different environments (development, staging, production). You can define environment variables in your workflow file or in your repository settings.

steps:
  - name: Deploy to Production
    uses: appleboy/ssh-action@master
    with:
      host: ${{ secrets.PRODUCTION_HOST }}
      username: ${{ secrets.PRODUCTION_USER }}
      key: ${{ secrets.PRODUCTION_KEY }}
      envs: NODE_ENV=production
      script: |
        cd /var/www/production
        git pull origin main
        npm ci
        npm run build
        pm2 restart my-app

Caching Dependencies

Caching dependencies can significantly speed up your build process. GitHub Actions provides a built-in caching mechanism.

steps:
  - uses: actions/checkout@v3
  - name: Use Node.js
    uses: actions/setup-node@v3
    with:
      node-version: '16.x'
      cache: 'npm'
  - name: Get yarn cache directory path
    id: yarn-cache-dir-path
    run: echo "::set-output name=dir::$(yarn cache dir)"
  - uses: actions/cache@v3
    id: yarn-cache
    with:
      path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
      key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
      restore-keys:
        ${{ runner.os }}-yarn-

Using Docker

Docker provides a consistent and isolated environment for your application. You can use Docker to build and deploy your application in a containerized environment.

steps:
  - uses: actions/checkout@v3
  - name: Build Docker image
    run: docker build -t my-app .
  - name: Push Docker image to Docker Hub
    run: |
      docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
      docker push my-app
  - name: Deploy to Server
    uses: appleboy/ssh-action@master
    with:
      host: ${{ secrets.PRODUCTION_HOST }}
      username: ${{ secrets.PRODUCTION_USER }}
      key: ${{ secrets.PRODUCTION_KEY }}
      script: |
        docker pull my-app
        docker stop my-app
        docker rm my-app
        docker run -d -p 80:3000 my-app

Testing Strategies

Comprehensive testing is crucial for a reliable CI/CD pipeline. Here are some testing strategies you should consider:

Unit Tests

Unit tests verify the functionality of individual components or functions in isolation.

Integration Tests

Integration tests verify the interaction between different components or modules.

End-to-End (E2E) Tests

E2E tests simulate real user scenarios to ensure that the entire application works as expected. Tools like Cypress or Playwright are commonly used for E2E testing.

Code Coverage

Measure code coverage to ensure that your tests are covering a significant portion of your codebase. Tools like Istanbul can be used to generate code coverage reports.

Monitoring and Alerting

Monitoring your CI/CD pipeline and setting up alerts is essential for identifying and resolving issues quickly. You can integrate GitHub Actions with monitoring tools like Datadog, New Relic, or Sentry.

You can also use GitHub Actions to send notifications to Slack or other communication channels when a build fails or a deployment is successful.

steps:
  - name: Send Slack notification
    uses: slackapi/slack-github-action@v1.18.0
    if: failure()
    with:
      channel-id: '#your-slack-channel'
      slack-token: ${{ secrets.SLACK_BOT_TOKEN }}
      github-token: ${{ secrets.GITHUB_TOKEN }}
      status: ${{ job.status }}

Conclusion

Building a robust CI/CD pipeline with GitHub Actions for automated deployment is a game-changer for software development teams. It streamlines the software delivery process, reduces errors, and allows for faster feedback and iteration. By understanding the key concepts of CI/CD, leveraging the power of GitHub Actions, and implementing advanced strategies like caching, Docker, and comprehensive testing, you can significantly improve your development workflow. Remember to prioritize security by using GitHub Secrets for sensitive information and to continuously monitor your pipeline for potential issues. Embrace automation, and let GitHub Actions empower your team to deliver high-quality software more efficiently.

#CI/CD#GitHub Actions#Automation#Deployment#DevOps
Share

Related Articles