Deploying Nuxt 3 to a VPS with Docker & Nginx
Shared hosting won't cut it for a Nuxt 3 SSR app. A VPS gives you full control — but with great power comes a somewhat longer deployment checklist. This guide walks you through every step, from writing the Dockerfile to getting that green padlock in the browser.
Prerequisites
- A VPS running Ubuntu 22.04 (DigitalOcean, Hetzner, or similar)
- Docker and Docker Compose installed on the server
- A domain name pointed at your server's IP
1. Dockerise Your App
Add a Dockerfile in your project root:
# Stage 1: build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.output ./ .output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
2. docker-compose.yml
version: '3.9'
services:
nuxt:
build: .
container_name: nuxt_app
restart: unless-stopped
environment:
- NUXT_PUBLIC_SITE_URL=https://yourdomain.com
networks:
- web
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certbot/www:/var/www/certbot
- ./certbot/conf:/etc/letsencrypt
depends_on:
- nuxt
networks:
- web
networks:
web:
driver: bridge
3. Nginx Configuration
Create nginx/conf.d/default.conf:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
gzip on;
gzip_types text/plain application/json text/css application/javascript;
location / {
proxy_pass http://nuxt:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
4. Get an SSL Certificate
# Start Nginx on port 80 first
docker compose up -d nginx
# Issue a certificate via Certbot
docker run --rm \
-v ./certbot/conf:/etc/letsencrypt \
-v ./certbot/www:/var/www/certbot \
certbot/certbot certonly --webroot \
--webroot-path=/var/www/certbot \
-d yourdomain.com -d www.yourdomain.com \
--email your@email.com --agree-tos --no-eff-email
5. Deploy Everything
# On your local machine — push the image or just SSH in and pull
git pull origin main
docker compose up -d --build
Your app is now live at https://yourdomain.com with automatic HTTP→HTTPS redirects and gzip compression enabled.
Auto-Renew SSL
Add this to your server's crontab (crontab -e):
0 3 * * * docker run --rm \
-v /home/deploy/certbot/conf:/etc/letsencrypt \
-v /home/deploy/certbot/www:/var/www/certbot \
certbot/certbot renew --quiet && \
docker exec nginx nginx -s reload
Wrapping Up
You now have a fully containerised, HTTPS-enabled Nuxt 3 application running behind an Nginx reverse proxy. This setup is portable, reproducible, and easy to scale — just add more services to your docker-compose.yml as your project grows.
