Skip to main content

Command Palette

Search for a command to run...

Setting Up Node.js CI/CD on EC2 with GitHub Actions and Nginx

Updated
8 min read
Setting Up Node.js CI/CD on EC2 with GitHub Actions and Nginx
S
Hi, I am Shivang Yadav, a Full Stack Developer and an undergrad BTech student from New Delhi, India.

Continuous Integration and Continuous Deployment (CI/CD) helps automate the process of testing and deploying code to production. In this blog, we’ll walk through setting up a CI/CD pipeline for a Node.js application using GitHub Actions and deploying it to an AWS EC2 instance.

Prerequisites

Before diving into the setup, ensure you have the following:

  1. AWS Account: You will need access to AWS with permission to create and manage EC2 instances.

  2. Node.js Application: We will assume you have a Node.js project already set up.

  3. GitHub Repository: The Node.js application should be in a GitHub repository.

Step 1: Launch an EC2 Instance

To begin, we’ll launch an EC2 instance that will serve as the deployment target for our Node.js application.

  1. Log in to your AWS Console and go to the EC2 Dashboard.

  2. Click on Launch Instance

  3. Choose an Amazon Machine Image (AMI). Select Ubuntu or Amazon Linux for Node.js applications.

  4. Choose the Instance Type. For a basic application, the free-tier eligible t2.micro is sufficient and setup a keypair

  5. Key Pair: Create a new key pair or use an existing one. Make sure to download the private key (.pem file), as you will need it to connect to the instance later.

  6. Configure Instance Details. In most cases, the default settings are fine.

  7. Add Storage. Use the default storage allocation (8 GB is typically enough for a small Node.js app).

  8. Configure Security Group:

    • Create a new security group and add the following rules:

      • SSH (port 22): Allow your IP address to connect via SSH.

      • HTTP (port 80): Allow access to your application.

    • You can also add HTTPS (port 443) if you plan to use SSL.

  9. Launch the Instance.

After the instance is launched, make a note of its Public IP address or Public DNS.

Step 2: Install Node.js and Set Up the Environment on EC2

Once the EC2 instance is running, connect to it using SSH and set up Node.js.

  1. Connect to the EC2 Instance: Open a terminal and run the following command to SSH into your instance:

     ssh -i /path/to/your-key.pem ubuntu@your-ec2-public-ip
    

  2. Update the Package List:

     sudo apt update
    
  3. Install Node.js: You can install the latest stable version of Node.js

     sudo apt-get install -y nodejs
    

    This installs the latest LTS (Long-Term Support) version of Node.js.

  4. Install Nginx: We’ll install Nginx, which we’ll use as a reverse proxy for our Node.js application.

    • Install Nginx:

        sudo apt-get install nginx
      
    • Start Nginx and enable it to run on startup:

        sudo systemctl start nginx
        sudo systemctl enable nginx
      
    • Check the status of Nginx to ensure it is running:

        sudo systemctl status nginx
      

At this point, Nginx should be running and accessible via the public IP of your EC2 instance. Open a browser and navigate to http://your-ec2-public-ip, and you should see the default Nginx welcome page.

  1. Install PM2: PM2 is a process manager for Node.js applications. It ensures that your application runs in the background and automatically restarts if it crashes.

     sudo npm install -g pm2
    

Step 3: Set Up GitHub Actions for CI/CD

Now, we’ll set up GitHub Actions to automatically deploy your Node.js application to the EC2 instance whenever you push changes to the main branch.

  1. Create a Self-Hosted Runner in GitHub:

    • Go to your GitHub repository, click on Settings > Actions > Runners, and click on New self-hosted runner button.

    • Select the OS type for your runner (Linux for an Ubuntu EC2 instance).

    • GitHub will show a set of instructions and a registration token, which you’ll use to set up the runner on your EC2 instance. Run those commands one by one on your EC2 instance.

    • Once you’ve completed the runner registration steps, you can install the runner as a systemd service.

      • Install the service by running the following command in your runner directory:

          sudo ./svc.sh install
        

        This command sets up the runner as a system service, so you don’t need to manually start it every time the EC2 instance is restarted or after logging out.

      • Start the Runner Service

          sudo ./svc.sh start
        

        This will start the self-hosted runner as a background service, and it will be ready to accept and execute workflows from your GitHub repository.

      • Check the Service Status

          sudo ./svc.sh status
        

        This command will show you whether the runner service is active. The output should display something like:

          ● actions.runner.your-repo.service - GitHub Actions Runner (your-repo)
             Loaded: loaded (/etc/systemd/system/actions.runner.your-repo.service; enabled; vendor preset: enabled)
             Active: active (running) since Thu 2024-10-10 14:00:00 UTC; 5min ago
             ...
        
  2. Create the GitHub Actions Workflow: Inside your Node.js project, create a directory .github/workflows/ and add a file named deploy.yml.

     name: Node.js CI/CD
    
     # Trigger the workflow on pushes to the "main" branch
     on:
       push:
         branches: [ "main" ]
    
     jobs:
       build:
    
         # This job runs on a self-hosted runner, in this case, your EC2 instance
         runs-on: self-hosted
    
         steps:
         # Step 1: Checkout the latest code from the GitHub repository
         - uses: actions/checkout@v4
    
         # Step 2: Setup Node.js environment using the specified version (e.g., 20.x)
         - name: Use Node.js 20.x
           uses: actions/setup-node@v3
           with:
             node-version: '20.x'  # Specify Node.js version
             cache: 'npm'          
    
         # Step 3: Install dependencies
         - run: npm ci
    
         # Step 4: Set up the environment variables by creating a .env file
         #         Secrets stored in GitHub will be used in the production environment
         - run: |
             touch .env
             echo "${{ secrets.PROD_ENV }}" > .env
    
         # Step 5: Build the project (only if a build script is present in package.json)
         - run: npm run build --if-present
    
         # Step 6: This step you have to update after setting up the nodejs application on ec2 instance
         #  Restart the Node.js app using PM2 for zero-downtime deployment
         # - run: pm2 restart node-app
    

    Workflow Overview

    • Trigger: The workflow is triggered every time code is pushed to the main branch of your GitHub repository.

    • Runner: The job is executed on a self-hosted runner, which is typically an EC2 instance or any server you control. This is where the application will be deployed.

  3. Verify Workflow Execution: After pushing your changes to the main branch and running the GitHub Actions workflow, your code should be deployed to the EC2 instance. GitHub Actions will place the code inside the _work folder in your runner directory.
    To access this folder:

    • SSH into your EC2 instance:

        ssh -i /path/to/your/key.pem ubuntu@your-ec2-ip
      
    • Navigate to the GitHub Actions runner directory:

      By default, the runner's working directory will be something like:

        cd ~/actions-runner/_work/<your-repo-name>/<your-repo-name>
      

      In this directory, you'll find the checked-out code from your repository after the GitHub Actions workflow runs.

  4. Setup Nginx as a Reverse Proxy: Next, you need to set up Nginx to act as a reverse proxy for your Node.js application. This ensures that requests to your EC2 instance (e.g., through its public IP or domain) are routed to your Node.js app.

    • Configure Nginx for your Node.js app:

      Open the Nginx configuration file for editing:

        sudo vim /etc/nginx/sites-available/default
      

      Replace the contents with the following Nginx configuration, adjusting it for your app’s port (usually 3000 or another port if configured):

        server {
            listen 80;
      
           server_name _;
      
           # Add this part to your file
            location /api {
                rewrite ^\/api\/(.*)$ /api/$1 break;
                proxy_pass http://localhost:4000; #assuming your nodejs appliction running on port 4000
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                }
        }
      

      The configuration specifies that any request path starting with /api will be forwarded to a backend Node.js application running on localhost:4000

    • Test the Nginx configuration:

      Test that your Nginx configuration is valid:

        sudo nginx -t
      

      If everything is correct, you should see a message like syntax is ok.

    • Restart Nginx to apply the changes:

        sudo systemctl restart nginx
      
  5. Start the Node.js App with PM2: To ensure your Node.js application is running, you’ll use PM2 to manage the process.

    • Start the Node.js application using PM2:

      In the same directory where your application is located (inside _work/<your-repo-name>), start your app with PM2:

        pm2 start server.js --name node-app  # Adjust `server.js` to your app's entry file
      

      This command will run the app in the background and associate it with the name node-app.

    • Check the status of the app:

      Use the following command to ensure that your app is running:

        pm2 status
      

      You should see your application listed with the status online.

    • Enable PM2 startup to ensure it starts on boot:

      Run the following command to configure PM2 to start your app automatically after a system reboot:

        pm2 startup
      

      Follow the on-screen instructions to complete the setup.

    • Save the PM2 process list:

      To save the current PM2 process list and restore it on reboot, use:

        pm2 save
      
  6. Verify the Setup: Now that Nginx is configured as a reverse proxy and your Node.js app is running under PM2:

    1. Visit your server's public IP or domain name in the browser:

       http://your_domain_or_public_ip/api
      

      You should see your Node.js application responding through Nginx.

    2. If everything is set up correctly, your app should now be accessible publicly, with Nginx forwarding incoming requests to your Node.js application.

    3. Now update your github workflow file step 6.

        - run: pm2 restart node-app
      

Step 4: Push Changes to GitHub

Now, whenever you push changes to the main branch, GitHub Actions will:

  • Run the build and tests.

  • If successful, deploy the updated code to your EC2 instance.

Try making a small change in your repository and pushing it:

git add .
git commit -m "Test CI/CD deployment"
git push origin main

You should see the GitHub Actions workflow running in the Actions tab of your repository. After it completes, the updated code will be deployed to your EC2 instance.


Step 5: Verify the Application

Finally, check that your Node.js application is running on the EC2 instance:

  1. Access the public IP or DNS of your EC2 instance in a web browser:

     http://your-ec2-public-ip/api
    
  2. You should see your Node.js application running!


Conclusion

You’ve now set up a CI/CD pipeline for your Node.js application using GitHub Actions and deployed it to an AWS EC2 instance. This automated process ensures that your code is tested and deployed seamlessly with every push to the main branch.

Feel free to expand this workflow by adding additional stages like database migrations, performance monitoring, or scaling your EC2 instances.

M

Everything looks good, but could you also configure or set up MongoDB in GitHub Actions while deploying to the EC2 instance?

More from this blog

S

Shivang Yadav

16 posts

Setting Up CI/CD for Node.js on EC2 with GitHub Actions and Nginx