Deploy de app Node.js na VPS com Docker
Deploy de aplicação Node.js na VPS com Docker garante que o ambiente de produção é idêntico ao de desenvolvimento — sem surpresas com versões de pacotes ou configuração do SO. Com Nginx como proxy reverso e HTTPS via Let's Encrypt, a aplicação fica acessível pelo domínio em menos de 10 minutos.
Dockerfile para Node.js em produção
O Dockerfile de produção usa multi-stage build para separar o ambiente de build do de execução — imagem final menor e sem dependências de desenvolvimento:
# Dockerfile
# Stage 1: instalação de dependências
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: build (para TypeScript ou bundlers)
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: imagem de execução (mínima)
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Usuário não-root para segurança
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=build --chown=appuser:appgroup /app/dist ./dist
COPY --from=build --chown=appuser:appgroup /app/package.json .
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]docker-compose.yml com Nginx e PostgreSQL
Stack completo para uma aplicação Node.js com banco de dados e proxy reverso:
services:
app:
build: .
restart: always
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
PORT: 3000
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- app_network
postgres:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
retries: 5
networks:
- app_network
nginx:
image: nginx:alpine
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- certbot_conf:/etc/letsencrypt
- certbot_www:/var/www/certbot
networks:
- app_network
volumes:
pg_data:
certbot_conf:
certbot_www:
networks:
app_network:Endpoint /health para healthcheck
O healthcheck do Docker precisa de um endpoint que responda 200 quando a aplicação está pronta para receber tráfego:
// src/routes/health.ts (ou .js)
import { Router } from 'express'
import { db } from '../database'
const router = Router()
router.get('/health', async (req, res) => {
try {
// Verificar conexão com banco (opcional mas recomendado)
await db.query('SELECT 1')
res.json({ status: 'ok', uptime: process.uptime() })
} catch (err) {
res.status(503).json({ status: 'error', message: 'Database unreachable' })
}
})
export default routerPipeline de deploy via SSH
Automatize o deploy com um script que envia o código e reinicia os containers:
#!/bin/bash
# deploy.sh — execute no seu computador local
VPS_USER=deploy
VPS_HOST=IP_DA_VPS
APP_DIR=/opt/minha-app
# 1. Sincronizar arquivos (exceto node_modules e .env)
rsync -avz --exclude='node_modules' --exclude='.env' --exclude='.git' \
./ $VPS_USER@$VPS_HOST:$APP_DIR/
# 2. Na VPS: rebuild e restart
ssh $VPS_USER@$VPS_HOST "
cd $APP_DIR
docker compose build app
docker compose up -d --no-deps app
docker compose ps
"
echo "Deploy concluído!"Nginx para proxy reverso da API Node.js
Configuração Nginx com keep-alive e timeout adequados para APIs Node.js:
# nginx.conf
upstream nodejs_app {
server app:3000;
keepalive 64;
}
server {
listen 80;
server_name api.seudominio.com.br;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name api.seudominio.com.br;
ssl_certificate /etc/letsencrypt/live/api.seudominio.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.seudominio.com.br/privkey.pem;
location / {
proxy_pass http://nodejs_app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
}
}$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.