Harnessing the Power of GitHub Actions and Docker for Effortless Next.js CI/CD

Setting Up Automated Build and Deployment Pipelines for Next.js Applications

Prabir Kalwani
6 min readApr 30, 2024

Working on small projects and deploying to cloud platforms like GCP and AWS or Your Own VPS ?

Implementing CI/CD can be a headache. In this blog, we’ll unravel the secrets of Continuous Integration and Continuous Deployment for Next.js apps. Streamline your development workflow and deploy , with a CI/CD pipeline tailored for the modern web using simple and beginner friendly tools such as GitHub Actions And Docker.

Prerequisites:

  1. A Virtual Cloud Machine from any cloud provider or a local Ubuntu VPS. In this case, we’ll be using Amazon Web Services (AWS) as our cloud provider.
  2. Docker, Git, and Docker Compose installed on the given machine.
  3. A NextJs Project You can check out a template here .
  4. DockerHub And A GitHub Account

To get started, we’ll launch an AWS EC2 instance and provide a bash script to install the required dependencies. This will ensure a smooth setup for implementing the CI/CD pipeline for our Next.js application.

#!/bin/bash

# install.sh
# to run this file first : chmod +x install.sh
# then you can run it using : ./install.sh

# Update your system
sudo apt update

# Install prerequisites
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add current user to docker group
sudo usermod -aG docker $USER

# Install Docker Compose
sudo apt install -y docker-compose

# Install Git
sudo apt install -y git

# Clean up
rm get-docker.sh

echo "Installation complete. Please reboot your system for changes to take effect."

5. Add Your Docker Hub Username and Docker Hub Access token into the repository Secrets by navigating into your repository > Settings > Secrets and Variables and add your repository secrets as show in the image below

Repository secrets

Understanding the CI/CD Workflow

To better grasp the workings of GitHub Actions, let’s explore a practical example using my Next.Js portfolio website.

flow diagram represents the CI/CD pipeline

1. Trigger:

  • The workflow is triggered by a push event to the main branch.

2. Build Job (build):

  • This job runs on the ubuntu-latest runner.
  • The workflow first checks out the source code using the actions/checkout@v4 action.
  • Then, it builds a Docker image using the docker build command. The image is tagged with prabirkalwani/portfolio:<latest>.
  • To push the image to Docker Hub, the workflow logs in using the provided Docker username and access token stored in secrets (presumably set up beforehand).
  • Finally, the built image is pushed to prabirkalwani/portfolio:latest on Docker Hub.

3. Deploy Job (deploy):

  • This job depends on the successful completion of the build job. It won't run unless the image is built successfully.
  • The deploy job runs on a self-hosted runner (meaning your own server or infrastructure).
  • It starts by pulling the latest image (prabirkalwani/portfolio:latest) from Docker Hub.
  • Next, it removes any existing container named prabir-website (assuming an old deployment was running).
  • Finally, the workflow runs the container in detached mode (-d) and maps the container port 3000 to the host port 3000. The container is named prabir-website for easier identification.

Overall Flow:

  1. You push code to the main branch.
  2. The workflow triggers the build job which builds and pushes the Docker image to Docker Hub.
  3. Once built, the deploy job pulls the image, removes any old container, and starts a new container running your application.

Setting Your CI/CD Pipeline:

Start By Completing the Prerequisites for this Project :

  1. Connect your GitHub Runner
    GitHub Runner offers a self-hosted virtual machine to run workflows
  2. Access your GitHub repository settings
    Click on “Actions”
    Click on “Runners”
    Click on “Add a new self-hosted runner”

Follow the steps and Connect your EC2 instance with GitHub
This will enable running CI/CD processes on the EC2 instance
The EC2 instance will act as the self-hosted runner for your repository

Runner settings

3. Configuring the self hosted runner application as a service
This will configure the self-hosted runner application as a service to automatically start the runner application when the machine starts.Follow the commands below :-

sudo ./svc.sh install
sudo ./svc.sh start

Lets write the code for the action that was explained above in this section :-

To set up the workflow, navigate to the Actions tab in your repository. Next, click on the New Workflow button and select the Docker configuration option.

Then, paste the following code snippet into the workflow editor:

name: Docker-Deploy

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Build docker image
run: docker build -t prabirkalwani/portfolio .
- name: Login to Docker Hub
run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Publish image to Docker Hub
run: docker push prabirkalwani/portfolio:latest

deploy:
needs: build
runs-on: self-hosted
steps:
- name: Pull image from Docker Hub
run: sudo docker pull prabirkalwani/portfolio:latest
- name: Delete old container
run: sudo docker rm -f prabir-website
- name: Run Docker container
run: sudo docker run -d -p 3000:3000 --name prabir-website prabirkalwani/portfolio:latest

You can modify the repository name and tag name to suit your project’s requirements. Additionally, you can modify the command is configured to monitor changes on the main branch, but you can alter this branch name as per your preferences.

4.Dockerfile for the NextJs Application

Lastly, we require a Dockerfile to containerize your Next.js application for deployment.

# Docker file for NextJS Application 
FROM node:20-alpine3.18 as builder

# Creating an app directory
WORKDIR /app

# copying the package-lock file
COPY package*.json ./

# running npm install here
RUN npm install --production

# copying rest of the files
COPY . .

# creating a build folder
RUN npm run build

# exposing the host port
EXPOSE 3000

# running the application
CMD ["npm", "run", "start"]

Upon committing changes, your continuous integration and continuous deployment (CI/CD) pipeline will be triggered, executing the following workflow steps.

Docker Workflow that we just created

Conclusion

This article is one of many I plan to create on Docker, DevOps, and related technologies that I’m currently exploring and learning. Sharing insights like these is genuinely enjoyable, and I look forward to creating more informative content as I further delve into containerization and DevOps practices.

Thank you for following along, If there are any better alternatives to this approach or suggestions feel free to reach out.

Bonus

Recently, I created a bash script that retrieves system vitals for a VPS, and I found the process immensely enjoyable. I encourage you to take a look at it as well.

#!/bin/bash

#system_info.sh file

# to run this file first : chmod +x system_info.sh
# then you can run it using : ./system_info.sh
# CPU Usage
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}')

# Disk Usage
DISK_USAGE=$(df -h | awk '$NF=="/"{printf "Disk Usage: %.2f%\n", $5}')

# Memory Usage
MEMORY_USAGE=$(free | awk '/Mem/{printf("Memory Usage: %.2f%\n"), $3/$2*100}')

# System Uptime
UPTIME=$(uptime -p)

# Load Average
LOAD_AVERAGE=$(uptime | awk -F'average:' '{ print $2 }' | awk '{ print $1, $2, $3 }')

# Display Information
echo "System Information:"
echo "-------------------"
echo "CPU Usage: $CPU_USAGE"
echo "Memory Usage: $MEMORY_USAGE"
echo "Disk Usage: $DISK_USAGE"
echo "Uptime: $UPTIME"
echo "Load Average: $LOAD_AVERAGE"
a working example

--

--

Prabir Kalwani

Curious scholar pursuing Tech and MBA. Committed to innovation, sustainability, adept in advanced web tech like ReactJS, MERN, MEAN.