ZyVOP Logo
Content That Connects
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZyVOP Logo
Content That Connects

The Developer Publishing Hub. Write once, cross-post to Dev.to, Medium, Hashnode, WordPress & Bluesky with automated canonical source tags and zero paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Developer API & CLI
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls · Full content ownership
All systems operational
HomeDitch Vercel: A Complete Guide to Auto-Deploying Next.js to a VPS via GitHub Actions

Ditch Vercel: A Complete Guide to Auto-Deploying Next.js to a VPS via GitHub Actions

Build a production-grade CI/CD pipeline for Next.js using GitHub Actions, Docker, PM2, Nginx, and a self-hosted VPS — without platform lock-in or unpredictable hosting costs.

ZyVOP
ZyVOP
Senior Developer
May 22, 2026
4 min read
Ditch Vercel: A Complete Guide to Auto-Deploying Next.js to a VPS via GitHub Actions
#Deployment Automation#GitHub Actions#CI/CD#VPS Hosting#Vercel Alternative
👍1

Vercel provides an unmatched developer experience. You push to main, and your site is live. But as your startup grows, Vercel's pricing limits—especially regarding serverless function execution time, background jobs, and bandwidth—can quickly force you into hundreds of dollars a month.

Moving to a cheap Virtual Private Server (VPS) on DigitalOcean, Hetzner, or AWS EC2 is the logical next step. A $10/month VPS can handle millions of requests a month. But giving up the "git push to deploy" magic is a tough pill to swallow.

In this guide, we are going to recreate that magic. We will configure GitHub Actions to securely SSH into your server, pull the code, utilize Docker build caching for blazing-fast builds, and securely inject your .env variables.


Prerequisites

  1. A Linux VPS with Docker and Docker Compose installed.

  2. Your application codebase on the server, running via docker-compose.yml.

  3. Your codebase hosted on GitHub.

Step 1: Secure Server Access via SSH Keys

GitHub Actions needs permission to log into your server. We will not use a password. We will generate a dedicated SSH key pair exclusively for GitHub.

SSH into your VPS and generate a new key:

ssh-keygen -t ed25519 -f ~/.ssh/github_actions_key -C "github_actions"

When prompted for a passphrase, leave it empty (press Enter).

Now, authorize this key to access your server by adding the public key to your authorized_keys file:

cat ~/.ssh/github_actions_key.pub >> ~/.ssh/authorized_keys

Display the private key and copy the entire output (including the BEGIN and END lines):

cat ~/.ssh/github_actions_key

Step 2: Configure GitHub Secrets

We must never hardcode server IPs or SSH keys into our codebase.

Go to your repository on GitHub. Navigate to Settings -> Secrets and variables -> Actions -> New repository secret.

Add the following three secrets:

  • SERVER_HOST: Your server's public IP address (e.g., 203.0.113.50).

  • SERVER_USERNAME: The user that runs your app (e.g., ubuntu or root).

  • SSH_PRIVATE_KEY: Paste the entire private key you copied in Step 1.

Handling .env Variables Securely

You likely have API keys or database URLs that your app needs. Do not commit a .env file to GitHub. Instead, create a fourth secret:

  • ENV_FILE: Paste the literal contents of your production .env file here.

Step 3: The GitHub Actions Workflow

In your local repository, create a directory path: .github/workflows/. Create a file named production-deploy.yml.

Paste the following configuration. I have commented on what each step does, including how we safely write the .env file and optimize Docker cache.

name: Deploy to Production VPS

# Trigger the workflow only when code is pushed to the main branch
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Execute Deployment Script via SSH
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          # Increase timeout for large docker builds
          timeout: 10m
          # We pass the ENV_FILE secret as an environment variable to the SSH session
          envs: ENV_FILE
          script: |
            # 1. Navigate to the project directory
            cd /opt/myapp

            # 2. Pull the latest code
            git reset --hard HEAD
            git pull origin main

            # 3. Securely write the .env file from GitHub Secrets
            # We use 'printenv' to safely output the multiline secret without breaking bash syntax
            printenv ENV_FILE > .env
            chmod 600 .env # Restrict read permissions

            # 4. Build the Docker image
            # We don't use --no-cache so Docker can reuse previously built layers (like npm install)
            docker compose build web

            # 5. Restart the container
            # -d runs it in the background
            docker compose up -d web

            # 6. Clean up dangling images to prevent the server's disk from filling up
            docker image prune -af --filter "until=24h"

Step 4: Optimizing Docker Caching (Crucial for Node.js)

If your deployments take 5 minutes because npm install runs every single time, your Dockerfile is optimized incorrectly.

To take advantage of the caching we enabled in the workflow, ensure your Dockerfile copies package.json before the rest of your code:

FROM node:18-alpine
WORKDIR /app

# Step A: Copy ONLY package files first
COPY package.json package-lock.json ./

# Step B: Install dependencies
RUN npm ci

# Step C: Copy the rest of the application
COPY . .

RUN npm run build
CMD ["npm", "start"]
\

Because Docker builds in layers, if you only change a React component (Step C), Docker realizes package.json hasn't changed. It instantly grabs the cached node_modules from Step B, entirely skipping the 2-minute npm install step. Your deployments will drop from 5 minutes to 15 seconds.

Rollback Strategy

What happens if you push a bug that breaks production? Because your code is on GitHub, rolling back is as simple as reverting the commit:

  1. On your local machine: git revert HEAD

  2. git push origin main

GitHub Actions will instantly trigger, SSH into your server, pull the reverted code, and deploy the stable version.

Conclusion

By combining GitHub Actions, SSH, and Docker, you have built a professional CI/CD pipeline. Your environment variables are injected securely on the fly, your builds are cached and fast, and old Docker images are pruned automatically.

You now have PaaS-level deployment infrastructure on an unmanaged server, saving you hundreds of dollars as you scale.

Comments (0)

Login to post a comment.

ZyVOP
ZyVOP

Founder of Zyvop 🚀 | Building AI-driven tools & premium insights for software engineers, CTOs, and tech leaders. Obsessed with automating workflows and exploring the frontier of AI.

Subscribe to ZyVOP's Newsletter

More from ZyVOP

View profile

Debian Adopts "Responsible Use of Generative AI" After Nine-Way Condorcet Vote

Debian's General Resolution 2026-002 closed on August 28 with "Responsible Use of Generative AI" beating eight rival proposals, including a Social Contract ban, by a clear Condorcet margin, per the project secretary's published beat matrix.

3 minAug 30

How I Built a Real-Time Developer Trend Radar Into My SEO Growth Engine

An AI-powered content intelligence system that streams live developer conversations from Hacker News, Dev.to, Google Search, and GitHub — and turns them into ready-to-write blog opportunities with one click.

12 minAug 29

Qwen3.8-Flash-Next Cost Efficiency, OpenExecutive Satire, and Multi-Vector Retrieval Advances

This week's digest covers Qwen3.8-Flash-Next's push for ultimate cost-efficiency, the viral OpenExecutive project, and the technical release of MultiVectorEncoder in Sentence-Transformers v6.0.

4 minAug 28

Anthropic’s Pricing Shock, Granite 4.2 Open‑Source Leap, and AI‑Powered Security & Policy Shifts

From Anthropic’s flagship model losing steam to IBM’s 512 K‑token Granite 4.2, plus a new wave of AI‑driven security exploits and policy alarms, this week’s digest maps the technical and market forces you need to act on now.

3 minAug 26

Introducing Questions and Discussions: A New Way to Connect!

We are thrilled to announce a major update to how you can interact and share content on our platform! Up until now, sharing your thoughts meant writing a standa...

2 minAug 1