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:
exposeno 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.