Como publicar Nuxt na VPS com Docker e HTTPS (preset Node, Caddy e variáveis de produção)

Fala aí, beleza? Subir um app Nuxt numa VPS parece tranquilo até tu perceber que o comando que funcionava na tua máquina não foi feito pra produção 😛
Tenha acesso root e SSD NVMe, com 10% OFF extra pelo link da Hora de Codar.
Ver a oferta de VPS Parceria Hora de CodarEste guia cobre um cenário bem específico: Nuxt 4.x, uma VPS com Docker Compose e um domínio próprio apontando pra ela
A ideia é ter dois containers, um com o servidor Node gerado pelo build e outro com o Caddy cuidando do HTTPS
E já fica o aviso logo de cara: nuxt dev é pra desenvolvimento e nuxt preview serve só pra pré-visualizar o build (o nuxt start é apenas um alias do preview), nenhum deles é servidor de produção!
Pré-requisitos para publicar Nuxt na VPS com Docker
Você precisa de quatro coisas: Node compatível com o Nuxt 4.x, uma VPS com Docker e acesso root, um domínio apontando pro servidor e as portas certas liberadas
Bora por partes 🙂
Qual versão do Node usar no Nuxt 4?
O requisito oficial do Nuxt 4.x é Node.js 22.x ou mais novo, com recomendação da versão LTS ativa (as de número par)
Se liga num detalhe: o Nuxt CLI v4, que chegou junto com o Nuxt 4.6 em outubro de 2026, exige Node v22.21+, v24.11+ ou v26+
Então, na hora de escolher a imagem Node do Dockerfile, pega uma tag que caia dentro dessas faixas
Qual VPS usar para o Nuxt com Docker?
A recomendação da Hora de Codar pra este projeto é a VPS da Hostinger (aviso: este artigo contém links de afiliado da Hostinger)
O motivo é técnico: o tutorial precisa de acesso root total pra rodar Docker e mexer em portas, e a VPS da Hostinger entrega isso com processadores AMD EPYC, armazenamento NVMe SSD e rede de 1 Gb/s em todos os planos, segundo a página oficial do serviço
Outro ponto que conversa direto com deploy: backups semanais gratuitos e recurso de snapshot, que te deixam reverter o servidor se um deploy der ruim
E tem um atalho massa: o template Ubuntu 24.04 com Docker, que já vem com docker-ce e docker-compose instalados
No hPanel o caminho é: VPS > Gerenciar > SO e Painel > Sistema Operacional, onde você escolhe o template Ubuntu 24.04 com Docker entre as opções de sistema disponíveis
Tome cuidado: a troca ou reinstalação do SO apaga permanentemente todos os dados atuais, inclusive o snapshot (mas não afeta os backups já existentes), então faz isso numa VPS nova ou logo no início, antes de ter algo importante lá
Domínio e portas
O domínio precisa ter os registros A (IPv4) e/ou AAAA (IPv6) apontando pro IP da VPS
E as portas 22 (SSH), 80 e 443 precisam estar liberadas, porque o Caddy usa 80 e 443 pra validar o domínio e servir o site
Passo a passo: Nuxt em Docker com Caddy e HTTPS
O fluxo é o mesmo de qualquer app em container atrás de proxy, se tu já viu como publicar uma API Go com Docker e HTTPS, vai reconhecer a lógica
- Configure o firewall da VPS Hostinger
Por que primeiro? Porque sem 80 e 443 abertas o Caddy não consegue emitir o certificado
No hPanel, dentro da seção do seu servidor VPS, acesse Firewall
O firewall gerenciado descarta todo o tráfego de entrada por padrão, então você precisa criar uma regra de aceitar para cada porta necessária: e a regra de SSH precisa vir primeiro, antes de qualquer outra, pra não perder o acesso ao servidor
Crie regras de aceitar para as portas nesta ordem:
- 22 (SSH), ANTES de qualquer outra coisa
- 80 (HTTP)
- 443 (HTTPS)
Os demais campos da regra (protocolo e outras opções) variam conforme a versão da tela no hPanel, então confira as opções disponíveis na própria interface no momento do seu acesso
Resultado esperado: firewall com as três regras de entrada criadas
O erro comum deste passo: criar a regra de SSH depois das outras, ou esquecer dela
No firewall gerenciado, por padrão todo o tráfego é descartado, então sem a regra de SSH tu perde o acesso ao servidor na hora
- Confirme o preset e gere o artefato de produção
O preset node-server é o padrão do Nitro quando nenhum preset é especificado nem detectado automaticamente: numa VPS genérica como esta, sem plataforma que acione autodetecção, se tu não trocou o preset no nuxt.config, já está no lugar certo
Rode o build (aqui assumindo que o script build do teu package.json chama nuxt build):
npm run build
Resultado esperado: o arquivo .output/server/index.mjs existe no projeto
Esse é o ponto de entrada que já sobe um servidor Node pronto, e o comando oficial pra rodar é:
NODE_ENV=production node .output/server/index.mjs
Por padrão ele escuta na porta 3000
Mais controle no próximo deploy.
Configure sua aplicação em uma VPS Hostinger com acesso root. Escolha os recursos conforme o que seu projeto precisa.
- SSD NVMe
- AMD EPYC
- Backups semanais
O erro comum deste passo: publicar com nuxt preview ou nuxt start
A documentação do comando preview deixa claro: ele existe pra pré-visualizar, carrega o .env no process.env e, em produção, quem define as variáveis é você
- Monte o Dockerfile multi-stage
A ideia aqui é separar: um estágio instala dependências e faz o build, o outro só roda o resultado
# Troque <TAG> por uma tag oficial do Node em 22.21+, 24.11+ ou 26+
FROM node:<TAG> AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:<TAG>
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.output ./.output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
E um .dockerignore pra não mandar lixo (nem segredo) pra imagem:
node_modules
.output
.env
Se teu projeto usa pnpm ou yarn, adapta as linhas de instalação
Antes de seguir, testa: sobe o container e confere se o app responde
Resultado esperado: a imagem builda e o container inicia o servidor na porta 3000
O erro comum deste passo: esquecer o NODE_ENV=production
Algumas dependências, como o Vue Router, só removem os avisos de desenvolvimento com essa variável, sem ela os logs podem ser inundados
- Defina as variáveis de produção no Compose
O runtimeConfig do Nuxt pode ser sobrescrito por variáveis de ambiente com prefixo
Primeiro declara as chaves no nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: '',
public: {
apiBase: ''
}
}
})
Aí, no docker-compose.yml, o prefixo decide quem enxerga o quê:
NUXT_sobrescreve valores doruntimeConfig, que só existem no servidorNUXT_PUBLIC_sobrescreve valores doruntimeConfig.public, que vão pro navegador
environment:
NODE_ENV: production
NUXT_API_SECRET: troque-por-um-segredo-real
NUXT_PUBLIC_API_BASE: https://api.seudominio.com
Porta e host do servidor também são variáveis: NITRO_PORT ou PORT (padrão 3000) e NITRO_HOST ou HOST (padrão 0.0.0.0)
Neste cenário os padrões já servem, só mexe se precisar
O erro comum deste passo: confiar no .env
Em produção o Nuxt não lê o arquivo .env, ele vale em desenvolvimento e no build, então as variáveis precisam vir do ambiente do container
E nunca coloque segredo em variável NUXT_PUBLIC_, porque tudo que está em public chega no cliente
- Deixe a porta 3000 só na rede interna
Que diferença faz ports ou expose? Toda!
portspublica a porta no host, ficando acessível por fora do containerexposenão publica a porta no host, deixando-a disponível apenas pros outros serviços dentro da rede Docker
Como quem fala com o Nuxt é o Caddy, o serviço app fica só com expose:
app:
build: .
environment:
NODE_ENV: production
NUXT_API_SECRET: troque-por-um-segredo-real
NUXT_PUBLIC_API_BASE: https://api.seudominio.com
expose:
- "3000"
O erro comum deste passo: colocar "3000:3000" em ports e deixar o app acessível direto pela internet, por fora do HTTPS
Usando expose, a porta 3000 fica restrita à rede interna criada pelo Compose, e só o Caddy (que está na mesma rede) consegue alcançá-la
- Configure o Caddy como proxy com HTTPS automático
O Caddy faz HTTPS automático quando sabe o domínio: emite o certificado, redireciona HTTP pra HTTPS e renova antes de expirar
Crie uma pasta caddy/ na raiz do projeto e, dentro dela, o arquivo Caddyfile:
seudominio.com {
reverse_proxy app:3000
}
O app ali é o nome do serviço no Compose, e o Docker resolve esse nome dentro da rede interna

Crie sistemas com IA para vender
Prompts, modelos e checklists para criar e publicar seu sistema com IA e preparar a oferta para o primeiro cliente.
Quero o e-book gratuito Receba o livro e os bônus no seu e-mail.Agora o docker-compose.yml completo:
services:
app:
build: .
environment:
NODE_ENV: production
NUXT_API_SECRET: troque-por-um-segredo-real
NUXT_PUBLIC_API_BASE: https://api.seudominio.com
expose:
- "3000"
caddy:
image: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy:/etc/caddy
- caddy_data:/data
- caddy_config:/config
depends_on:
- app
volumes:
caddy_data:
caddy_config:
Na imagem oficial do Caddy, os certificados e chaves ficam em /data (fundamental persistir) e as configs ficam em /config; o Caddyfile padrão mora em /etc/caddy, então a forma confirmada de usar teu próprio arquivo é montar a pasta caddy/ inteira nesse caminho, como no Compose acima
E o Nitro servindo HTTPS sozinho? Ele até consegue, se receber NITRO_SSL_CERT e NITRO_SSL_KEY, mas a própria documentação do Nitro recomenda isso só pra teste, com o Nitro atrás de um proxy reverso que termina o SSL em produção
Ou seja, a emissão e a renovação do certificado viram problema teu, enquanto o Caddy já faz isso automático
Neste cenário, deixa o Nuxt em HTTP na rede interna e o HTTPS fica com o Caddy 🙂
O erro comum deste passo: não persistir o /data
Sem volume, cada recriação do container joga fora os certificados e o Caddy precisa emitir tudo de novo
- Suba e valide
Na VPS, dentro da pasta do projeto:
docker-compose up -d --build
Agora confere três coisas:
https://seudominio.comabre com cadeado válido no navegadorhttp://seudominio.comredireciona pra HTTPS (dá pra ver comcurl -I http://seudominio.com)- os logs dos dois containers estão limpos
docker-compose logs app
docker-compose logs caddy
Resultado esperado: site no ar em HTTPS e nenhum acesso direto pela porta 3000
Manutenção a cada deploy
Mudou o código? Rebuilda a imagem com o mesmo docker-compose up -d --build, porque o .output nasce dentro do build
E antes de mudanças grandes (troca de versão do Node, do Nuxt ou do proxy), cria um snapshot da VPS no hPanel, assim tu tem pra onde voltar
Erros comuns ao publicar Nuxt com Docker e Caddy
Quase tudo que dá errado nesse setup é configuração, não servidor fraco
Então nada de trocar de plano por causa de erro de config haha
Certificado HTTPS não é emitido
Causa: o domínio não aponta pra VPS (registros A/AAAA errados) ou as portas 80 e 443 estão fechadas no firewall
Correção: confere os registros DNS e as regras do firewall, e veja o log com docker-compose logs caddy
Prevenção: configura DNS e firewall antes do primeiro up
Sem 80 e 443 acessíveis, o Caddy não consegue completar a validação padrão do certificado; resolver isso por outro caminho é uma configuração que fica fora do escopo deste guia
Variável não aparece no app
Causa: a variável está só no .env, está sem o prefixo NUXT_ ou NUXT_PUBLIC_, a chave não foi declarada no runtimeConfig ou é um valor privado sendo lido no cliente
Correção: declara a chave no nuxt.config, passa a variável no environment do Compose com o prefixo certo e recria o container
Prevenção: lembra da regra: privado só no servidor, public vai pro navegador
Caddy retorna erro de upstream
Causa: o reverse_proxy aponta pra um nome de serviço ou porta diferente do que o Compose usa, ou alguém mudou o NITRO_PORT e esqueceu do Caddyfile
Correção: alinha o nome do serviço (app) e a porta (3000, ou a que estiver no NITRO_PORT) nos dois arquivos
Prevenção: se não precisa mudar a porta, não muda 😛
Logs inundados de avisos
Causa: o container roda sem NODE_ENV=production
Correção: define a variável no Dockerfile ou no environment e recria o container
Prevenção: deixa no Dockerfile, assim não depende de alguém lembrar no Compose
App acessível direto pela porta 3000
Causa: o serviço app usa ports em vez de expose
Correção: troca por expose: ["3000"]
Prevenção: no serviço do app, a regra é não publicar nada no host
Próximo passo depois do deploy
Com o site no ar, a prioridade é endurecer o servidor: o firewall já está lá, então vale somar outras camadas de proteção, como o Fail2ban
Depois cria um snapshot da VPS logo após o primeiro deploy funcionando, porque ele vira teu ponto de retorno conhecido
E documenta o rebuild num README curto: tag do Node usada, variáveis esperadas (sem os valores dos segredos) e o comando de deploy, assim o próximo deploy não depende da tua memória
Se tu prefere gerenciar deploys por painel em vez de Compose na mão, dá uma olhada em como instalar o Coolify numa VPS
E se ainda vai contratar o servidor, dá pra conferir a VPS para este projeto e já começar pelo template com Docker
Até o próximo post! 🙂
Perguntas frequentes
O Nuxt 5 já está disponível pra esse tipo de deploy?
Não, o Nuxt 5 ainda não é estável e o roadmap prevê o lançamento pro 4º trimestre de 2026 (estimativa). A versão atual é a linha 4.6 do Nuxt, iniciada em outubro de 2026 junto com o Nuxt CLI v4, que é a base deste guia.
O Caddy emite certificado HTTPS se eu usar só o IP da VPS, sem domínio?
Não, o HTTPS automático do Caddy depende do domínio estar presente na configuração, porque é assim que ele sabe pra quem emitir o certificado. Sem domínio apontando pro servidor, não dá pra seguir o fluxo deste guia; resolver isso exigiria outra configuração, fora do escopo deste tutorial.
Quer aprender mais sobre inteligência artificial e programação na prática? Conheça o canal Hora de Codar no YouTube e acompanhe os próximos vídeos.
Formações
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 48min
Blog | Mais populares

Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]

Checklist de segurança n8n VPS 2026: proteja sua automação
Confira o checklist de segurança n8n VPS 2026 e mantenha sua automação protegida. Veja práticas essenciais e atualizadas para seu servidor. Manter seu ambiente de […]
Quanto custa uma VPS? Preço atualizado 2026
Quando se fala em quanto custa uma VPS, é preciso contextualizar o tema a partir de um provedor específico, porque o preço só faz sentido […]
