Deploy a Node.js or Next.js App to a VPS with InfraPilot
InfraPilot Team
March 8, 2026
When to Move Off Managed Platforms
Vercel and Netlify are excellent for deploying static sites and serverless functions. But once your app needs a persistent database, WebSocket connections, background job queues, or long-running processes, managed platforms become awkward and expensive fast. A VPS with Docker and InfraPilot gives you full control without the serverless constraints.
Containerise Your App
First, add a Dockerfile to your project. For a Next.js app:
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
For a plain Node.js/Express app, the Dockerfile is simpler:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
The Compose File
Create docker-compose.yml on your VPS:
services:
app:
image: ghcr.io/your-org/your-app:latest
restart: unless-stopped
environment:
DATABASE_URL: ${DATABASE_URL}
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
NEXTAUTH_URL: https://app.yourdomain.com
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Set Up the Reverse Proxy and SSL
In InfraPilot, go to Traffic → Proxy Hosts → Add Proxy Host:
- Domain:
app.yourdomain.com - Forward host:
app, port:3000 - Enable WebSocket support if your app uses real-time features
- Enable Let's Encrypt SSL and force HTTPS
InfraPilot handles the Nginx config and certificate issuance automatically — no manual certbot or Nginx edits needed.
CI/CD: Auto-Deploy on Push
For automatic deployments, build and push to GitHub Container Registry in your CI pipeline:
# .github/workflows/deploy.yml
- name: Build and push
run: |
docker build -t ghcr.io/your-org/your-app:latest .
docker push ghcr.io/your-org/your-app:latest
- name: Deploy via SSH
run: |
ssh user@your-server "cd ~/apps/myapp && docker compose pull && docker compose up -d"
Monitor Your App in InfraPilot
Once deployed, InfraPilot gives you live container metrics, unified logs from the app and database, and alerting if either container restarts unexpectedly. The web terminal lets you run database migrations or debug sessions without leaving your browser.
Related posts
The Best Self-Hosted Docker Dashboards in 2026 (Honestly Compared)
Portainer, Dockge, Yacht, Lazydocker, InfraPilot — there are more Docker management UIs than ever. Here's an honest breakdown of which one fits which use case, from CLI-lovers to teams that want a full web dashboard.
Nginx Proxy Manager vs InfraPilot: Is There a Better Alternative?
Nginx Proxy Manager is the most popular Docker reverse proxy GUI — but it only manages proxies and SSL. If you're already running Docker containers, there's a case for combining your proxy management with your container dashboard.
Portainer vs InfraPilot: The Honest Docker Management Comparison (2026)
Portainer is great — but it doesn't manage Nginx, SSL, or give you traffic analytics. Here's an honest comparison of when to use Portainer, when to use InfraPilot, and what actually matters for single-server Docker setups.