How to Set Up CI/CD for Next.js with GitHub Actions, Docker, and a VPS

By Rakibul IslamJuly 29, 20265 views

Deploying a Next.js application manually every time you make a change can quickly become repetitive.

A better approach is to set up a CI/CD pipeline that automatically deploys your application whenever you push new code to your GitHub repository.

In this guide, we'll build a practical CI/CD workflow using:

  • Next.js
  • GitHub Actions
  • Docker
  • Dockerfile
  • SSH
  • Ubuntu VPS
  • Nginx
  • HTTPS/SSL

The main focus of this guide is automated CI/CD deployment. Nginx is included only as the production reverse proxy that sits in front of the Dockerized Next.js application.

By the end of this tutorial, you will be able to push code to the main branch and have GitHub Actions automatically connect to your VPS, update the project, build a new Docker image, and restart the production container.


What Is CI/CD?

CI/CD stands for Continuous Integration and Continuous Delivery/Deployment.

Instead of manually logging into your VPS after every update, a CI/CD pipeline can automate the deployment process.

Without CI/CD:

text
Developer
   ↓
git push
   ↓
Login to VPS manually
   ↓
git pull
   ↓
Install dependencies
   ↓
Build application
   ↓
Restart application

With CI/CD:

text
Developer
   ↓
git push origin main
   ↓
GitHub Actions
   ↓
SSH into VPS
   ↓
Pull latest code
   ↓
Build Docker image
   ↓
Restart Docker container
   ↓
Application Live

This makes deployments faster, more consistent, and less dependent on manual server work.


What We Are Going to Build

Our final CI/CD architecture will look like this:

text
Developer
    │
    │ git push origin main
    ▼
GitHub Repository
    │
    ▼
GitHub Actions
    │
    │ SSH
    ▼
Ubuntu VPS
    │
    ├── Git
    │
    ├── Docker
    │
    └── Nginx
            │
            ▼
      Docker Container
            │
            ▼
        Next.js App

The actual deployment flow will be:

text
git push
    ↓
GitHub Actions starts
    ↓
SSH connection to VPS
    ↓
git fetch origin
    ↓
git reset --hard origin/main
    ↓
docker build
    ↓
Stop old container
    ↓
Remove old container
    ↓
Start new container
    ↓
Next.js application is live

Requirements

Before starting, make sure you have:

  • A Next.js project
  • A GitHub repository
  • An Ubuntu VPS
  • SSH access to your VPS
  • Docker installed on the VPS
  • Nginx installed on the VPS
  • A domain name pointing to your VPS
  • Git installed on the VPS

You should also have a working Next.js application before setting up CI/CD.


Step 1: Prepare Your Next.js Project

First, make sure your Next.js application works correctly locally.

For example:

bash
pnpm install

Then run the development server:

bash
pnpm dev

Open:

text
http://localhost:3000

Once your application works correctly, prepare it for a production Docker deployment.


Step 2: Configure Next.js for Docker

Open your Next.js configuration file.

For a TypeScript configuration:

text
next.config.ts

Add:

typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;

If you're using next.config.js:

javascript
const nextConfig = {
  output: "standalone",
};

module.exports = nextConfig;

The standalone output creates a production-ready Next.js server that works well with Docker.


Step 3: Create a Dockerfile

Create a Dockerfile in the root of your Next.js project:

bash
nano Dockerfile

Add:

dockerfile
FROM node:24-alpine AS base

FROM base AS deps

WORKDIR /app

COPY package.json pnpm-lock.yaml ./

RUN corepack enable && pnpm install --frozen-lockfile


FROM base AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules

COPY . .

RUN corepack enable && pnpm build


FROM base AS runner

WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000

CMD ["node", "server.js"]

This uses a multi-stage Docker build.

The Dockerfile separates the process into:

  1. Dependency installation
  2. Next.js production build
  3. Production runtime

This keeps the final image cleaner and avoids carrying unnecessary build dependencies into the runtime image.


Step 4: Create a .dockerignore File

Create:

bash
nano .dockerignore

Add:

text
node_modules
.next
.git
.gitignore
README.md
Dockerfile
.env
.env.local
npm-debug.log
pnpm-debug.log

This prevents unnecessary files from being sent to the Docker build context.


Step 5: Test the Docker Build Locally

Before creating the CI/CD pipeline, test your Docker image locally.

Build the image:

bash
docker build -t nextjs-app .

Check the image:

bash
docker images

Run the container:

bash
docker run -d \
  --name nextjs-app \
  -p 3000:3000 \
  nextjs-app

Check the container:

bash
docker ps

Then open:

text
http://localhost:3000

If your application works correctly, the Docker setup is ready.


Step 6: Prepare the VPS

Connect to your VPS:

bash
ssh root@YOUR_VPS_IP

Update the server:

bash
sudo apt update
sudo apt upgrade -y

Install Git:

bash
sudo apt install git -y

Check Git:

bash
git --version

Step 7: Install Docker on the VPS

If Docker is not already installed, run:

bash
curl -fsSL https://get.docker.com | sh

Check Docker:

bash
docker --version

Check whether Docker is running:

bash
systemctl status docker

If necessary, start Docker:

bash
systemctl start docker

Enable Docker to start automatically after a server reboot:

bash
systemctl enable docker

Step 8: Clone Your Next.js Project on the VPS

Create a directory for your application:

bash
mkdir -p /var/www

Go to the directory:

bash
cd /var/www

Clone your GitHub repository:

bash
git clone https://github.com/USERNAME/YOUR-REPOSITORY.git

Enter your project:

bash
cd YOUR-REPOSITORY

Check the project:

bash
ls

You should see files such as:

text
package.json
pnpm-lock.yaml
Dockerfile
next.config.ts
app/
public/

Step 9: Test the Docker Deployment on the VPS

Before automating deployment, make sure the application works on the VPS.

Build the Docker image:

bash
docker build -t nextjs-app .

Run the container:

bash
docker run -d \
  --name nextjs-app \
  --restart unless-stopped \
  -p 3000:3000 \
  nextjs-app

Check:

bash
docker ps

Test the application:

bash
curl http://127.0.0.1:3000

If you receive an HTML response, your Next.js application is running successfully inside Docker.


Step 10: Install Nginx

Nginx is not the CI/CD system.

It is used here as a reverse proxy to receive requests from your domain and forward them to the Next.js Docker container.

Install Nginx:

bash
sudo apt update
sudo apt install nginx -y

Check Nginx:

bash
systemctl status nginx

Your architecture will now look like:

text
Internet
   ↓
Domain
   ↓
Nginx
   ↓
127.0.0.1:3000
   ↓
Docker Container
   ↓
Next.js

Step 11: Configure Nginx Reverse Proxy

Create a configuration file:

bash
sudo nano /etc/nginx/sites-available/nextjs-app

Add:

nginx
server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Replace example.com with your actual domain.


Step 12: Enable the Nginx Configuration

Create the symbolic link:

bash
sudo ln -s /etc/nginx/sites-available/nextjs-app /etc/nginx/sites-enabled/

Test the configuration:

bash
sudo nginx -t

You should see:

text
syntax is ok
test is successful

Reload Nginx:

bash
sudo systemctl reload nginx

Now your domain should forward requests to the Next.js Docker container.


Step 13: Configure HTTPS

Install Certbot:

bash
sudo apt install certbot python3-certbot-nginx -y

Run:

bash
sudo certbot --nginx -d example.com -d www.example.com

After SSL is configured, your application will be available through:

text
https://example.com

Step 14: Create the GitHub Actions Workflow

Now we reach the main part of this tutorial: CI/CD.

GitHub Actions will automatically run whenever you push code to the main branch.

Inside your project, create:

text
.github/workflows/deploy.yml

You can create it using:

bash
mkdir -p .github/workflows
nano .github/workflows/deploy.yml

Add:

yaml
name: Deploy Next.js

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1.2.0

        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USERNAME }}
          key: ${{ secrets.VPS_SSH_KEY }}
          port: ${{ secrets.VPS_PORT }}

          script: |
            set -e

            echo "===== DEPLOYMENT START ====="

            echo "User:"
            whoami

            echo "Home:"
            echo $HOME

            echo "Going to project directory..."

            cd /var/www/your-nextjs-project

            echo "Pulling latest code..."

            git fetch origin
            git reset --hard origin/main

            echo "Building Docker image..."

            docker build -t nextjs-app .

            echo "Stopping old container..."

            docker stop nextjs-app || true
            docker rm nextjs-app || true

            echo "Starting new container..."

            docker run -d \
              --name nextjs-app \
              --restart unless-stopped \
              -p 3000:3000 \
              nextjs-app

            echo "===== DEPLOYMENT COMPLETE ====="

Replace:

text
/var/www/your-nextjs-project

with the actual project directory on your VPS.


Step 15: Understand What the GitHub Actions Workflow Does

Let's break down the workflow.

Trigger

yaml
on:
  push:
    branches:
      - main

This means the workflow runs whenever code is pushed to the main branch.

For example:

bash
git add .
git commit -m "Update application"
git push origin main

That push triggers the deployment workflow.


Connect to the VPS

yaml
uses: appleboy/ssh-action@v1.2.0

This action allows GitHub Actions to connect to your VPS using SSH.

The credentials come from GitHub Secrets:

yaml
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}

Pull the Latest Code

The workflow runs:

bash
git fetch origin
git reset --hard origin/main

This makes the VPS project match the latest main branch.


Build the Docker Image

Next:

bash
docker build -t nextjs-app .

Docker reads the Dockerfile and creates a new production image containing the latest version of the Next.js application.


Stop the Old Container

The workflow then runs:

bash
docker stop nextjs-app || true
docker rm nextjs-app || true

This removes the previous running container.

The || true prevents the deployment from failing if the container does not exist yet.


Start the New Container

Finally:

bash
docker run -d \
  --name nextjs-app \
  --restart unless-stopped \
  -p 3000:3000 \
  nextjs-app

The new version of your Next.js application starts inside Docker.


Step 16: Configure GitHub Secrets

Go to your GitHub repository:

text
Settings
→ Secrets and variables
→ Actions
→ New repository secret

Create these secrets:

text
VPS_HOST
VPS_USERNAME
VPS_SSH_KEY
VPS_PORT

For example:

text
VPS_HOST
YOUR_VPS_IP

VPS_USERNAME
root

VPS_PORT
22

For:

text
VPS_SSH_KEY

add your private SSH key.

Never commit your private SSH key directly to your GitHub repository.


Step 17: Push the CI/CD Workflow

Commit the workflow:

bash
git add .github/workflows/deploy.yml

Create a commit:

bash
git commit -m "Add CI/CD deployment workflow"

Push it:

bash
git push origin main

GitHub Actions should now start automatically.


Step 18: Monitor the GitHub Actions Deployment

Open your GitHub repository and go to:

text
GitHub Repository
→ Actions
→ Deploy Next.js

You should see the deployment job running.

A successful deployment should look like:

text
Deploy Next.js
    ✓ Deploy to VPS

If the workflow fails, open the job logs to identify the failed command.


Step 19: Verify the Docker Container

After the GitHub Actions workflow completes successfully, connect to your VPS:

bash
ssh root@YOUR_VPS_IP

Check the container:

bash
docker ps

You should see:

text
nextjs-app

Check application logs:

bash
docker logs nextjs-app

For live logs:

bash
docker logs -f nextjs-app

Step 20: Test the Production Website

Open your production domain:

text
https://example.com

If the latest changes are visible, your CI/CD pipeline is working successfully.

Now every time you run:

bash
git push origin main

GitHub Actions will automatically deploy the new version to your VPS.


Complete CI/CD Architecture

Your final architecture is:

text
                         DEVELOPER
                             │
                             │ git push
                             ▼
                    GITHUB REPOSITORY
                             │
                             ▼
                     GITHUB ACTIONS
                             │
                             │ SSH
                             ▼
                         UBUNTU VPS
                             │
                 ┌───────────┴───────────┐
                 │                       │
                 ▼                       ▼
               Nginx                  Docker
                 │                       │
                 │                       ▼
                 │                Next.js Container
                 │                       │
                 └──────────► Production App

The CI/CD pipeline itself is:

text
Developer
    │
    │ git push origin main
    ▼
GitHub
    │
    ▼
GitHub Actions
    │
    │ SSH
    ▼
VPS
    │
    ▼
git fetch origin
    │
    ▼
git reset --hard origin/main
    │
    ▼
docker build
    │
    ▼
Stop Old Container
    │
    ▼
Remove Old Container
    │
    ▼
Start New Container
    │
    ▼
Production Updated

Common Problems and Solutions

GitHub Actions Cannot Connect to the VPS

Check:

  • VPS_HOST
  • VPS_USERNAME
  • VPS_PORT
  • VPS_SSH_KEY

Also make sure SSH access works manually.

bash
ssh root@YOUR_VPS_IP

Docker Container Is Not Running

Check:

bash
docker ps -a

Then:

bash
docker logs nextjs-app

The logs should show why the container stopped.


Nginx Shows 502 Bad Gateway

First check Docker:

bash
docker ps

Then test Next.js directly:

bash
curl http://127.0.0.1:3000

If Next.js doesn't respond, check:

bash
docker logs nextjs-app

If Next.js works but Nginx doesn't, test the Nginx configuration:

bash
sudo nginx -t

Port 3000 Is Already in Use

Check:

bash
sudo lsof -i :3000

Also check Docker:

bash
docker ps

Stop the process or container that is already using the port.


GitHub Actions Deployment Fails During Docker Build

Check the Docker build manually on the VPS:

bash
cd /var/www/your-nextjs-project
docker build -t nextjs-app .

This makes it easier to identify whether the issue is with the Dockerfile or the CI/CD workflow.


Docker vs PM2 in CI/CD

PM2 and Docker can both be used to run a Next.js application, but they work differently.

With PM2:

text
Nginx
   ↓
PM2
   ↓
Next.js

With Docker:

text
Nginx
   ↓
Docker
   ↓
Next.js

If you're using Docker for your production deployment, you generally don't need PM2 inside the Docker container.

Docker manages the container lifecycle, while Nginx acts as the reverse proxy.

For this CI/CD setup, the responsibilities are separated:

text
GitHub Actions → Deployment automation
Docker         → Application container
Nginx          → Reverse proxy
Next.js        → Application
VPS            → Server infrastructure

Why Use CI/CD for Next.js?

A CI/CD pipeline provides several advantages.

Faster Deployments

You don't have to manually log into the VPS after every code change.

Fewer Manual Errors

The same deployment commands run every time.

Consistent Builds

Docker creates the application using the same environment and build process.

Easy Rollout

Pushing to the main branch can automatically trigger a production deployment.

Better Developer Experience

Your workflow becomes:

bash
git add .
git commit -m "Update feature"
git push origin main

The rest is automated.


Security Best Practices

When creating a CI/CD pipeline, security is important.

Never Commit Secrets

Do not put passwords, private SSH keys, API keys, or production credentials inside your repository.

Use GitHub Secrets instead.

Use SSH Keys

SSH keys are preferable to password-based authentication for automated deployments.

Protect Your Main Branch

Consider enabling branch protection so that production deployments only happen after your required checks pass.

Keep Docker and Server Updated

Regularly update your VPS, Docker installation, and application dependencies.

Use HTTPS

Always serve production applications through HTTPS.


Final Deployment Workflow

Once everything is configured, your normal development workflow becomes very simple:

bash
git add .
git commit -m "Update application"
git push origin main

Then:

text
Git Push
   ↓
GitHub Actions
   ↓
SSH → VPS
   ↓
Pull Latest Code
   ↓
Docker Build
   ↓
Stop Old Container
   ↓
Start New Container
   ↓
Nginx
   ↓
Next.js Live

You no longer need to manually run the deployment process after every update.


Conclusion

Setting up CI/CD for a Next.js application doesn't have to be complicated.

With GitHub Actions, Docker, SSH, and a VPS, you can automate the entire deployment process.

The final system separates each responsibility clearly:

  • GitHub stores your source code.
  • GitHub Actions automates deployment.
  • SSH securely connects GitHub Actions to your VPS.
  • Docker packages and runs your Next.js application.
  • Nginx handles reverse proxying and production web traffic.
  • Next.js runs your application.

The most important part is the automated pipeline:

text
Developer
    ↓
git push origin main
    ↓
GitHub Actions
    ↓
SSH → VPS
    ↓
Pull latest code
    ↓
Docker build
    ↓
Restart container
    ↓
Application Live

Once this is configured, deploying a new version can be as simple as:

bash
git push origin main

Push your code → GitHub Actions runs → Docker rebuilds → VPS updates → Your Next.js application goes live automatically.

Article Info

CategoryWeb Dev
PublishedJuly 29, 2026
Views5 views
About The Author

I am Rakibul Islam, a Next.js and React.js Developer based in Dhaka, Bangladesh. I specialize in building fast, modern web applications using Next.js, React.js, TypeScript, Tailwind CSS and the MERN Stack. With 12+ months of experience and 25+ clients, I help businesses create high-performance websites. Available for freelance projects in Dhaka and worldwide.