How to Deploy a Next.js App to a VPS Without Docker
Docker is powerful — but for a single Next.js app on a $6 VPS, it's overkill. Learn the PM2 + Nginx stack that uses half the RAM, deploys in seconds, and is easier to debug.
Why deploy Next.js without Docker?
Docker solves real problems — dependency isolation, reproducible builds, orchestration at scale. But when you're running a single Next.js app on a VPS, those problems don't exist yet. What you get instead is extra complexity and resource overhead on a machine that has neither to spare.
No Docker daemon eating 100–200 MB of RAM. Your 1 GB VPS stays free for your actual app.
Skip image builds entirely. A git pull + npm run build + pm2 reload takes 20–30 seconds.
Logs are in PM2, config is in Nginx. No container layers, no docker exec, no volume mounts to trace.
A $6 VPS with 1 GB RAM can comfortably run Next.js with PM2. Add Docker and you're already tight.
Docker vs PM2 + Nginx: side-by-side
Here's how the two approaches compare for a typical Next.js app on a single VPS:
| Metric | Docker | PM2 + Nginx |
|---|---|---|
| RAM usage (idle) | 300–500 MB | 100–180 MB |
| Deploy speed | 2–5 min (image build) | 20–40 sec (git pull + build) |
| Setup complexity | Dockerfile, compose, networking | ecosystem.config.js + Nginx conf |
| Debugging | docker logs, exec, inspect | pm2 logs, standard Linux tools |
| Cold start | 5–10 sec (container init) | 1–2 sec (process start) |
| Disk usage | 500 MB–1 GB+ (images + layers) | 100–200 MB (app + node_modules) |
When Docker makes sense (and when it doesn't)
This isn't anti-Docker. Docker is excellent — for the right use cases. Here's a quick decision framework:
The PM2 + Nginx stack explained
Instead of Docker, you use two battle-tested tools that have been running Node.js apps in production for over a decade:
The request flow: Client → Nginx (443) → PM2 → Next.js (3000)
Step-by-step: deploy Next.js to a VPS without Docker
This guide uses Ubuntu 22.04/24.04 on any VPS provider (Hetzner, DigitalOcean, Hostinger, etc.). The entire process takes about 15 minutes.
1. Server setup
Start with a fresh Ubuntu VPS. Create a non-root user and configure the firewall:
# Create a deploy user adduser deploy usermod -aG sudo deploy # Configure firewall ufw allow OpenSSH ufw allow 'Nginx Full' ufw enable # Switch to deploy user su - deploy
Install Node.js via NVM:
# Install NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash source ~/.bashrc # Install Node.js LTS nvm install --lts nvm use --lts # Verify node -v # v22.x.x npm -v # 10.x.x
2. Clone and build your Next.js app
# Clone your repo git clone https://github.com/you/my-nextjs-app.git /var/www/my-app cd /var/www/my-app # Install production dependencies npm ci # Set environment variables cp .env.example .env nano .env # add your production values # Build for production npm run build
3. PM2 ecosystem config
Create an ecosystem.config.js in your project root. This tells PM2 how to run your app:
module.exports = {
apps: [
{
name: "my-nextjs-app",
script: "node_modules/.bin/next",
args: "start",
cwd: "/var/www/my-app",
instances: 1,
autorestart: true,
max_memory_restart: "512M",
env: {
NODE_ENV: "production",
PORT: 3000,
},
},
],
};# Install PM2 globally npm install -g pm2 # Start the app with PM2 pm2 start ecosystem.config.js pm2 save # Enable auto-start on server reboot pm2 startup # Run the command PM2 outputs
4. Nginx reverse proxy config
Install Nginx and create a server block that proxies traffic to your Next.js app:
sudo apt update && sudo apt install -y nginx
# /etc/nginx/sites-available/my-app
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
location / {
proxy_pass http://localhost: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;
proxy_cache_bypass $http_upgrade;
}
# Cache Next.js static assets
location /_next/static {
proxy_pass http://localhost:3000;
add_header Cache-Control "public, max-age=31536000, immutable";
}
}# Enable the site sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/ sudo rm /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx
5. SSL with Certbot
Get a free SSL certificate from Let's Encrypt. Certbot auto-configures Nginx for HTTPS:
# Install Certbot sudo apt install -y certbot python3-certbot-nginx # Obtain SSL certificate (auto-modifies Nginx config) sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # Verify auto-renewal sudo certbot renew --dry-run
6. Auto-deploy with GitHub webhooks
Create a simple webhook script so every push to main triggers a redeploy:
#!/bin/bash # /var/www/deploy.sh cd /var/www/my-app git pull origin main npm ci npm run build pm2 reload my-nextjs-app
You can use a lightweight webhook server like webhook or a simple Express endpoint to trigger this script. Or — skip the scripting entirely and let DeployWise handle it.
Resource comparison on a $6 VPS (1 GB RAM)
Here's what a real Next.js app looks like on a budget VPS with and without Docker:
That's 240 MB saved — a 63% reduction in memory usage. On a 1 GB VPS, that's the difference between comfortable headroom and OOM kills under load.
The automated way: DeployWise
Every step above — Node.js installation, PM2 config, Nginx reverse proxy, SSL, and GitHub webhook deploys — is exactly what DeployWise automates for you. No Docker required.
You get all the benefits of bare-metal performance (no Docker overhead) with the convenience of a managed platform. Free tier available.
Frequently asked questions
Can I deploy Next.js without Docker?
Yes. Next.js is a Node.js application at its core. You can run it directly on any Linux server using PM2 as a process manager and Nginx as a reverse proxy. No containers needed.
Is deploying without Docker less secure?
Not inherently. Docker adds isolation, but a properly configured VPS with firewall rules (UFW), non-root users, and regular updates is equally secure for most applications. Many production Node.js apps run without containers.
When should I use Docker instead of PM2 + Nginx?
Docker shines when you need reproducible environments across teams, run multiple apps with conflicting dependencies, or use orchestration tools like Kubernetes. For a single Next.js app on a VPS, PM2 + Nginx is simpler and uses fewer resources.
How much RAM does Next.js need without Docker?
A typical Next.js app uses 80-150 MB of RAM when running directly with PM2. The same app in a Docker container uses 200-400 MB due to the container runtime overhead. A $6 VPS with 1 GB RAM can comfortably run a Next.js app without Docker.
Can I still use GitHub Actions CI/CD without Docker?
Absolutely. You can set up GitHub webhooks or GitHub Actions to SSH into your VPS, pull the latest code, rebuild, and reload PM2 — all without Docker. DeployWise automates this entire workflow for you.
Deploy Next.js without Docker — in one click
DeployWise sets up PM2, Nginx, and SSL on your VPS automatically. No containers, no Dockerfile, no DevOps knowledge needed.
Deploy Now — Free