Como publicar uma aplicação Astro com SSR e Docker em uma VPS

Ilustração de uma aplicação Astro com SSR sendo publicada em contêiner Docker dentro de uma VPS

Fala aí, beleza? Se o seu projeto Astro precisa de login, formulário, dado atualizado a cada requisição ou alguma lógica no servidor, só subir a pasta de build não resolve 🙂

Aqui o caminho é este: adapter Node em modo standalone, imagem Docker multi-stage baseada na receita oficial e um proxy reverso cuidando do HTTPS na VPS

Você vai precisar de um projeto Astro atualizado, Docker, uma VPS com acesso root e um domínio já apontado para o IP do servidor

Site estático ou aplicação SSR: quando você precisa do adapter Node

Você só precisa do adapter Node quando alguma página ou endpoint for renderizado no servidor, no momento da requisição

Por padrão, o Astro pré-renderiza o site inteiro e manda HTML estático pro navegador. É o output: 'static', a saída padrão (guia de renderização sob demanda)

Na prática, existem três cenários:

  • Site 100% estático: fica no output: 'static' e não roda Node em produção. A receita oficial de Docker do Astro serve o build com Apache httpd na porta 80 ou com NGINX, que exige criar o arquivo nginx/nginx.conf na raiz do projeto
  • Site majoritariamente estático com algumas rotas dinâmicas: continua em output: 'static' e você coloca export const prerender = false no topo da página ou do endpoint que precisa ser dinâmico. O resto segue estático, mas essas rotas já exigem um adapter
  • Aplicação inteira sob demanda: output: 'server' com o adapter Node, e todo o projeto passa a ser renderizado no servidor

Se liga: renderização sob demanda sem adapter não existe

Em VPS com Node, o adapter é o @astrojs/node. Além das rotas sob demanda, ele suporta server islands, actions e sessions (guia do adapter Node)

Standalone ou middleware?

O @astrojs/node tem dois modos:

  • standalone: gera um servidor que inicia sozinho quando o módulo de entrada é executado
  • middleware: pra encaixar o Astro dentro de outro servidor Node, como Express ou Fastify

Este tutorial usa standalone porque é exatamente o que o Dockerfile oficial espera: um processo Node subindo sozinho dentro do contêiner, sem servidor extra no meio

Se você conhece um app Node "normal" que roda com node server.js, é bem semelhante 😀

O que você precisa antes de começar

Antes de abrir o terminal, confira esta lista:

  • Projeto Astro na linha atual: a versão estável é da linha 7, com a 7.4.0 publicada em 15/10/2026 (releases do Astro)
  • Node compatível: desde o Astro 6, Node 18 e Node 20 não são mais suportados, e o mínimo passou a ser Node 22.12.0, tanto no desenvolvimento quanto no deploy. Na versão atual da linha 7 ([email protected], publicada em 15/10/2026), esse mínimo subiu para Node 22.22.0 (releases do Astro): confirme a exigência da sua versão instalada antes de fixar a imagem
  • Docker instalado na máquina onde você vai gerar a imagem
  • VPS com acesso root, pra instalar e controlar os contêineres
  • Domínio apontado para o IP da VPS, que você vai usar no passo do proxy reverso com HTTPS

Qual VPS usar para este projeto?

A recomendação da Hora de Codar para este cenário é a VPS da Hostinger (este artigo contém links de afiliado da Hostinger)

O motivo é a tarefa: o tutorial depende de Docker, proxy reverso e acesso total ao servidor, e a Hostinger reúne essas peças prontas: template Docker, Docker Manager e um guia próprio para o Nginx Proxy Manager

  • Acesso root, com controle total do servidor
  • Template Ubuntu 24.04 que já vem com docker-ce e docker-compose instalados
  • Docker Manager, ferramenta web incluída sem custo nas VPS com o template Docker, pra implantar e monitorar contêineres sem linha de comando
  • Nginx Proxy Manager, implantado pelo Catálogo do próprio Docker Manager, com emissão de certificado Let’s Encrypt gratuito pela aba SSL
  • Processadores AMD EPYC e SSD NVMe
  • Rede de 1 Gbps, com servidores em várias partes do mundo
  • Backups semanais gratuitos

Como referência de especificação, o plano KVM 2 tem 2 vCPU, 8 GB de RAM e 100 GB de disco NVMe. Isso é só referência, não um mínimo obrigatório: o tamanho certo depende do tráfego e do que a sua aplicação faz

Passo a passo: do adapter Node ao HTTPS na VPS

O resultado final: sua aplicação Astro rodando em um contêiner Node na VPS, acessível pelo seu domínio em HTTPS

Bora ver na prática?

Passo 1: instalar o adapter Node

  1. Na raiz do projeto, rode:
npx astro add node

Esse comando instala o @astrojs/node e já ajusta o astro.config numa etapa só

Resultado esperado: o pacote aparece nas dependências e o adapter fica registrado na configuração

O erro comum deste passo: pular o adapter e esperar que prerender = false ou output: 'server' funcionem sozinhos. Sem adapter não tem renderização sob demanda

Passo 2: configurar o astro.config.mjs

  1. Defina o modo de saída, o adapter em standalone e os domínios permitidos. Exemplo didático, troque o domínio pelo seu:
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
  security: {
    allowedDomains: [
      { hostname: 'exemplo.com.br', protocol: 'https' }
    ]
  }
});

Se o site é quase todo estático, mantenha o output padrão e marque só as rotas dinâmicas no frontmatter da página:

---
export const prerender = false;
---

E por que o allowedDomains?

A aplicação vai ficar atrás de um proxy reverso. A opção security.allowedDomains define os padrões de host permitidos nas requisições SSR: protocol, hostname e port: e, com ela configurada, o Astro valida o cabeçalho X-Forwarded-Host contra esses padrões (referência de configuração)

Tome cuidado com versão antiga: antes da 5.14.2, o Astro refletia o valor de X-Forwarded-Host em Astro.url sem validação, falha registrada como CVE-2025-61925. Mais um motivo pra estar na linha atual

Resultado esperado: o build passa a gerar a parte de servidor em dist/server/

O erro comum deste passo: não configurar allowedDomains com o domínio real da sua aplicação. Como o Astro valida X-Forwarded-Host contra os padrões dessa lista, um domínio ausente ou errado pode fazer Astro.url não refletir o host que você esperava: depois do deploy, sempre confira esse valor numa rota real

Passo 3: criar o .dockerignore

  1. Crie um arquivo .dockerignore na raiz do projeto

Ele funciona como o .gitignore: define arquivos que o Docker ignora nos comandos COPY e ADD. A receita oficial recomenda esse arquivo porque acelera o build e reduz o tamanho da imagem

Liste ali o que não precisa ir pra dentro da imagem, como dependências e builds gerados na sua máquina

Resultado esperado: a etapa de envio de contexto do build fica mais leve

Passo 4: escrever o Dockerfile multi-stage

  1. Use como base o Dockerfile de SSR da receita oficial, que exige o @astrojs/node e serve o site com Node.js na porta 4321

Que multi-stage? É dividir o build em estágios, cada um com uma função, e copiar pra imagem final só o que interessa. A estrutura oficial é esta:

  • base: FROM node:lts AS base, copia package.json e package-lock.json
  • prod-deps: instala apenas as dependências de produção
  • build-deps: instala as dependências de build
  • build: copia o projeto e executa o build
  • runtime: copia o node_modules de produção e o dist

O esqueleto abaixo mostra essa estrutura. Complete as linhas de instalação e cópia de cada estágio com os comandos da receita oficial:

FROM node:lts AS base
# copia package.json e package-lock.json

FROM base AS prod-deps
# instala só as dependências de produção (comando na receita oficial)

FROM base AS build-deps
# instala as dependências de build (comando na receita oficial)

FROM build-deps AS build
# copia o projeto e roda o build

FROM base AS runtime
# copia node_modules de prod-deps e dist de build
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD ["node", "./dist/server/entry.mjs"]

O grande ganho aqui é o cache: quando só o código-fonte muda, o Docker não reinstala as dependências npm, o que pode economizar minutos em cada build

Se você já empacotou outro runtime, a lógica é parecida com a de hospedar Bun com Docker

Resultado esperado: uma imagem que, ao iniciar, roda node ./dist/server/entry.mjs escutando na porta 4321

O erro comum deste passo: esquecer o ENV HOST=0.0.0.0 no Dockerfile. A receita oficial inclui essa variável ao lado de PORT=4321, então mantenha as duas exatamente como no Dockerfile oficial pra não ter surpresa na hora de ligar o proxy

Outro ponto: a imagem node:lts precisa atender ao Node mínimo exigido pela sua versão do Astro. Desde o Astro 6 esse mínimo é 22.12.0, mas na linha 7 atual ([email protected]) ele subiu para 22.22.0: confira qual versão a tag lts está apontando no momento do build e fixe a versão da imagem se precisar garantir o mínimo exigido

Passo 5: variáveis de ambiente e segredos

  1. Organize as variáveis antes de subir o contêiner

No Astro, as variáveis são acessadas com import.meta.env. Em SSR, elas podem ser lidas em tempo de execução conforme o adapter, e na maioria dos adapters isso é feito via process.env (guia de variáveis de ambiente)

Pra segredos, a API astro:env permite definir um schema tipado. Segredo de servidor usa context: 'server' com access: 'secret', e o helper getSecret() lê o valor bruto de uma variável

No modo standalone, HOST e PORT também podem ser sobrescritos em runtime pelas variáveis de ambiente, sem rebuildar a imagem

Vai receber upload? O @astrojs/node tem a opção bodySizeLimit (adicionada na versão 10.0.0 do adapter), que define o tamanho máximo do corpo da requisição no servidor standalone. O padrão é 1 GB, então ajuste pro que faz sentido no seu caso

O erro comum deste passo: esperar que um segredo marcado como servidor apareça no código do cliente. Ele foi feito justamente pra não aparecer

Passo 6: preparar a VPS com o template Docker

  1. No hPanel da Hostinger, acesse VPS → Manage → OS & Panel → Operating System e escolha o template com Docker entre os sistemas operacionais disponíveis

Esse template entrega Ubuntu 24.04 com docker-ce e docker-compose já instalados (documentação do template Docker)

Tome cuidado! Trocar ou reinstalar o sistema operacional apaga permanentemente todos os dados da VPS, inclusive o snapshot (documentação sobre troca de SO). Faça isso antes de ter qualquer dado importante na máquina, senão é um rm -rf da vida sem você ter digitado nada haha

Resultado esperado: VPS com Docker pronto e o Docker Manager disponível no painel

Ainda não tem o servidor? Dá pra comparar os planos de VPS da Hostinger e escolher conforme o tamanho do projeto

Passo 7: subir o contêiner com o Docker Manager

  1. Abra o Docker Manager no painel da VPS

Ele é a interface web pra implantar, iniciar, parar e monitorar contêineres, e consegue subir imagens publicadas no Docker Hub sem linha de comando (artigo da Hostinger sobre o Docker Manager)

Se você publicar a imagem do passo 4 num registry como o Docker Hub, use esse caminho direto no Docker Manager. Ele também tem as opções "Compose manually" e "Compose from URL", úteis quando você prefere não publicar a imagem antes de implantar. Configure as variáveis de ambiente do passo 5 em qualquer um desses caminhos

Resultado esperado: contêiner do Astro em execução, aparecendo como ativo no Docker Manager

Passo 8: proxy reverso com HTTPS

  1. Instale o Nginx Proxy Manager pelo próprio Docker Manager, seguindo o guia da Hostinger

E por que um proxy na frente? Ele recebe o tráfego do domínio em HTTPS e repassa pro contêiner do Astro, que segue falando HTTP só dentro do servidor

A configuração fica assim:

  • Coloque o contêiner do Astro na mesma rede Docker do Nginx Proxy Manager
  • Crie o proxy host apontando para o nome do contêiner do Astro na porta 4321
  • Na aba SSL, emita o certificado Let’s Encrypt gratuito

Encaminhar pelo nome do contêiner, em vez de IP e porta expostos, é a prática recomendada pela Hostinger quando vários serviços ficam atrás do mesmo proxy

Se depois a sua stack crescer com API e banco, o raciocínio é o mesmo de uma API NestJS com Compose

O erro comum deste passo: apontar o proxy direto para IP e porta do contêiner em vez da rede Docker. O guia da Hostinger usa IP:porta como fluxo básico, mas recomenda a rede Docker com o nome do contêiner quando há vários serviços atrás do mesmo proxy

Checagem final

Confira estes três pontos:

  • O domínio abre em https:// com certificado válido
  • Uma rota com prerender = false (ou qualquer rota, se você usa output: 'server') responde de forma dinâmica, com conteúdo gerado na requisição
  • Astro.url reflete o domínio que você configurou em allowedDomains

Passou nos três? Aplicação no ar, mto massa! 😀

Erros comuns ao publicar Astro SSR com Docker e como resolver

A maioria dos problemas aqui é de configuração, não de servidor. Antes de pensar em trocar de plano, confira esta lista

O build ou o start falha por versão do Node

  • Sintoma: erro logo no build ou ao iniciar o contêiner
  • Causa: Node antigo. Desde o Astro 6, Node 18 e 20 não são suportados; na versão atual do Astro (linha 7, [email protected]), o mínimo subiu para Node 22.22.0
  • Solução: use uma imagem que atenda ao mínimo exigido pela sua versão exata do Astro
  • Como prevenir: fixe a versão da imagem no Dockerfile em vez de depender de uma tag que muda sozinha

O proxy não alcança a aplicação

  • Sintoma: erro de gateway ou timeout ao abrir o domínio
  • Causa: servidor sem HOST=0.0.0.0, porta diferente de 4321 ou contêiner fora da rede do proxy
  • Solução: confira HOST e PORT no contêiner e coloque o Astro na mesma rede Docker do Nginx Proxy Manager
  • Como prevenir: aponte o proxy sempre para o nome do contêiner na porta certa

Host ou URLs errados atrás do proxy

  • Sintoma: Astro.url não mostra o seu domínio
  • Causa: security.allowedDomains sem o domínio correto configurado
  • Solução: configure hostname e protocol do seu domínio
  • Como prevenir: inclua essa opção já no primeiro deploy, como no passo 2, e confira o valor de Astro.url numa rota real depois do deploy

Upload ou corpo de requisição rejeitado

  • Sintoma: requisições grandes falham no servidor
  • Causa: limite definido em bodySizeLimit no @astrojs/node
  • Solução: ajuste o valor conforme o tamanho que sua aplicação realmente precisa receber

A página que deveria ser dinâmica sai estática

  • Sintoma: o conteúdo não muda entre requisições
  • Causa: a rota foi pré-renderizada, porque falta export const prerender = false ou output: 'server'
  • Solução: marque a rota ou mude o modo de saída do projeto

Integrações incompatíveis depois de atualizar

  • Sintoma: erro de build após subir a versão do Astro
  • Causa: Astro e adapter em versões que não conversam entre si
  • Solução: rode npx @astrojs/upgrade, que atualiza o Astro e as integrações oficiais, incluindo o adapter, para versões compatíveis

Vídeo complementar do canal

No vídeo tem o Hermes Desktop, um app com interface gráfica pra gerenciar o agente de IA Hermes, rodando local ou em uma VPS

Conteúdo complementar do Hora de Codar para conhecer o canal; não é uma demonstração específica desta notícia ou versão.

Próximo passo: manter a aplicação no ar

Com a aplicação publicada, o que importa agora é atualizar sem medo

Monte esta rotina pra cada atualização:

  1. Confirme a data do último backup da VPS antes de mexer em qualquer coisa: a Hostinger faz backups semanais grátis, mas vale garantir que o mais recente já cobre o estado atual do seu projeto
  2. Rode npx @astrojs/upgrade pra alinhar Astro e adapter
  3. Rebuilde a imagem, publique e reimplante o contêiner
  4. Abra o domínio de novo e confira o HTTPS e uma rota dinâmica

Quando essa rotina ficar repetitiva, a API de VPS da Hostinger é o caminho pra automatizar deploys e gerenciar a infraestrutura. Mas comece pela rotina manual, beleza?

Se ainda vai montar o ambiente, dá pra conferir a VPS para este projeto e seguir o passo a passo desde o template Docker

Até o próximo post! 🙂

Perguntas frequentes

Dá para gerar a imagem Docker direto na VPS, sem usar outra máquina?

Dá, já que a VPS com o template Docker da Hostinger tem acesso root e docker-ce instalado, então o build pode rodar ali dentro. Pra implantar pelo Docker Manager, como no passo 7, você pode publicar a imagem num registry como o Docker Hub, ou usar as opções "Compose manually" e "Compose from URL", que não exigem ter a imagem publicada antes

O Nginx Proxy Manager substitui a necessidade de configurar allowedDomains no Astro?

Não, são camadas diferentes. O Nginx Proxy Manager cuida do HTTPS e do certificado Let’s Encrypt na frente do contêiner, enquanto security.allowedDomains é quem valida o cabeçalho X-Forwarded-Host dentro do próprio Astro, então os dois precisam estar configurados.


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.

Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 48min

Blog | Mais populares