Pular para o conteúdo
Flashti
Voltar pro blog
Dev & Infra2 min de leiturapor Hiago da Flashti

Publicar um site Next.js na VPS com Docker e Caddy: o jeito limpo

Publicar um app é onde muita gente trava — não por falta de habilidade, mas por falta de método. Eu uso um padrão simples há anos: Docker + Caddy numa VPS. O Caddy resolve SSL sozinho (Let's Encrypt automático), e o Docker garante que o app sobe igual em qualquer lugar.

A estrutura

projeto/
├─ Dockerfile
├─ docker-compose.yml
├─ .dockerignore
└─ src/

Dockerfile minimalista

FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
CMD ["npm", "start"]

E o .dockerignore:

node_modules
.next
.git
.env.local

docker-compose com Caddy

services:
  web:
    build: .
    restart: unless-stopped
    expose:
      - "3000"
    networks: [edge]

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    networks: [edge]

networks:
  edge:
    external: true

volumes:
  caddy_data:

O detalhe: o serviço web não expõe porta no host (expose apenas). Só o Caddy fala com o mundo — porta 80 e 443 — e faz proxy pro container certo pela rede edge. Num host com vários apps, isso evita briga de porta e centraliza o SSL.

Caddyfile

flashti.com, www.flashti.com {
	reverse_proxy web:3000
}

Salvou o arquivo e o Caddy busca o certificado sozinho. Sem Nginx config, sem certbot manual, sem "expirou de novo".

Pulo do gato: expose no compose + Caddy na ponta = uma porta só abrindo pro mundo. Firewall agradece, e adicionar o segundo projeto vira só mais um bloco no Caddyfile, sem tocar no primeiro.

Deploy rápido

scp -r ./build user@vps:/opt/meu-app
ssh user@vps "cd /opt/meu-app && docker compose up -d --build"

Isso não é o deploy definitivo (pra isso existe CI/CD), mas já é o suficiente pra colocar algo real no ar hoje, com HTTPS funcionando.

FAQ

Preciso de um domínio próprio?

Pra TLS automático do Caddy, sim. Pra teste local você roda só o Docker mesmo.

E banco de dados?

Rode Postgres/Redis como containers no mesmo compose, com volume persistente. Separado do app, mas gerenciável junto.

É seguro pra produção?

É o começo seguro: imagem enxuta, restart automático, SSL e só uma porta exposta. O passo seguinte é firewall, usuário sem root e backups — que é outro post.

Bora colocar isso no teu negócio?

Se você quer esse tipo de publicação com SSL, monitoramento e backup sem dor de cabeça, é literalmente o que eu faço todo dia. Me chama no WhatsApp e a gente tira seu sistema do ar pra valer.

Gostou? Vamos tirar isso do papel

Cada artigo aqui é sistema que eu construo todo dia, na medida certa pro teu negócio. Se alguma dessas ideias faz sentido pra você, me chama que a gente conversa sobre o teu caso — sem compromisso.