Como hospedar SvelteKit em uma VPS com adapter-node e Docker

Ilustração de um container Docker conectado a um servidor VPS representando a hospedagem de uma aplicação SvelteKit

Fala aí, beleza? O SvelteKit 3 saiu oficialmente em 1º de outubro de 2026, e muito tutorial de deploy que tá por aí simplesmente parou de funcionar 😛

Em 01/11/2026, a versão mais recente do @sveltejs/kit é a 3.1.1, lançada em 29/10/2026. A versão mais recente do @sveltejs/adapter-node é a 6.0.1, lançada na mesma data: um patch que corrige um bug de resposta text/event-stream com headers imutáveis e já declara dependência do kit 3.1.1

As mudanças estruturais que quebram guia antigo vieram do SvelteKit 3 e do adapter-node 6.0.0, os dois lançados em 01/10/2026: o guia de migração oficial do SvelteKit 3 documenta que o svelte.config.js não é mais suportado, e o changelog do adapter-node 6.0.0 registra que ele agora exige SvelteKit 3 e remove a variável de ambiente ORIGIN

Aqui o recorte é bem específico: projeto SvelteKit 3, servidor Node gerado pelo adapter, container Docker numa VPS e um proxy reverso na frente. Tudo com base na documentação oficial do adapter-node, bora?

O que você precisa antes de fazer o deploy do SvelteKit na VPS

Antes de tudo, o projeto precisa estar no SvelteKit 3, porque o adapter-node 6 exige essa versão

As versões mínimas pedidas pelo SvelteKit 3, segundo o guia de migração oficial, são:

  • Node 22.17
  • TypeScript 6
  • Svelte 5.57.1
  • Vite 8.0.12
  • @sveltejs/vite-plugin-svelte 7

Se o projeto ainda tá no SvelteKit 2, a migração vem primeiro (já já mostro o comando)

Do lado do servidor, você precisa de uma VPS com Docker e acesso pra administrar o ambiente

Pra esse cenário, a recomendação principal da Hora de Codar é a VPS da Hostinger (este artigo contém links de afiliado da Hostinger)

O motivo é técnico, ligado direto à tarefa:

  • Acesso root: você instala e administra o que precisar no servidor
  • Template Docker: template Ubuntu 24.04 com docker-ce e docker-compose pré-instalados
  • Docker Manager: interface web no painel, disponível nos planos VPS com template Docker, pra subir compose sem depender só do terminal
  • Infra: virtualização KVM, armazenamento SSD NVMe e processadores AMD EPYC
  • Firewall: gerenciamento de firewall pelo painel, que vai ser útil pra fechar a porta do container

Vale lembrar: a documentação da Hostinger descreve o Docker Manager como um recurso self-managed: ou seja, subir e administrar os containers pelo compose, seja pelo painel ou por SSH, fica por sua conta, que é exatamente o que os passos abaixo mostram

Como configurar o adapter-node no SvelteKit 3

A configuração do adapter agora mora no vite.config.js (ou .ts), como opção do plugin sveltekit()

Nada de svelte.config.js, beleza? Bora no passo a passo:

  1. Migre o projeto, se ainda estiver no SvelteKit 2. Rode o comando oficial:
npx sv migrate sveltekit-3

Ele faz a parte mecânica e deixa uma lista de pendências, então leia essa lista com calma antes de seguir

  1. Instale o adapter-node como dependência de desenvolvimento:
npm i -D @sveltejs/adapter-node
  1. Configure o adapter no plugin sveltekit(), importando de @sveltejs/kit/vite e do pacote do adapter:
// vite.config.ts
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import adapter from '@sveltejs/adapter-node';

export default defineConfig({
  plugins: [
    sveltekit({
      adapter: adapter(),
      paths: {
        origin: 'https://seudominio.com.br'
      }
    })
  ]
});
  1. Defina paths.origin com a URL pública do app. Antes, a origem era controlada pela variável de ambiente ORIGIN (e, pra prerender, pela opção kit.prerender.origin). No adapter-node 6, isso foi substituído pela opção paths.origin, que agora mora direto no sveltekit() do vite.config, como opção de primeiro nível do plugin, e não mais dentro de kit.*

E por que isso importa? O guia de migração do SvelteKit 3 explica que paths.origin deve refletir a origem pública do app quando ela não pode ser derivada com confiança dos cabeçalhos da requisição, que é exatamente o caso atrás de proxy reverso. É esse mesmo valor que entra na checagem de CSRF de envio de formulários e chamadas de remote functions

Um detalhe que vale ter em mente: como essa opção é lida pelo Vite durante o npm run build, a expectativa é que, ao mudar o domínio, seja preciso gerar um build (e uma imagem Docker) novos pra que o valor atualize: a documentação não detalha esse comportamento de cache especificamente, mas é o que se espera de uma opção resolvida em tempo de build

Erro comum deste passo: copiar de tutorial antigo o svelte.config.js ou a variável ORIGIN no ambiente. Nenhum dos dois funciona no adapter-node 6

Resultado esperado: um vite.config válido, com o adapter e a origem dentro do sveltekit() 🙂

Como gerar o build e separar as dependências de produção

O npm run build gera o servidor de produção no diretório de saída do adapter, que por padrão é build

Só que essa pasta sozinha não roda nada

Pra subir em produção, você precisa de três coisas:

  • o diretório de saída (build)
  • o package.json do projeto
  • as dependências de produção em node_modules

As dependências de produção saem copiando o package.json e o package-lock.json e rodando:

npm ci --omit dev

Agora a decisão que mais pega gente desprevenida: onde colocar cada pacote

  • devDependencies: o pacote é empacotado dentro do app com Rolldown
  • dependencies: o pacote fica externo e, como regra geral, PRECISA existir em node_modules no servidor

É meio contraintuitivo, né? Mas faz sentido: o que tá em dependencies o bundle deixa de fora e espera achar instalado na hora de rodar

Tem uma exceção que vale guardar: o changelog do adapter-node 6 registra que bibliotecas Svelte listadas em dependencies são empacotadas (bundled) mesmo assim, em vez de externalizadas. Então a regra "dependencies = externo" vale pra dependências em geral, não pra bibliotecas Svelte

Pra checar localmente, sobe o servidor:

node build

Por padrão ele aceita conexões em 0.0.0.0, na porta 3000

Erro comum deste passo: colocar em dependencies um pacote (que não seja biblioteca Svelte) sem ele estar instalado na imagem. O servidor falha ao importar o módulo assim que tenta usar ele

E mais uma pegadinha do adapter-node 6: a lista de assets estáticos é registrada no build

Arquivo jogado na pasta de saída depois do build não é servido, então se precisar de um asset novo, gere o build de novo

Dockerfile e docker compose para SvelteKit com adapter-node

O Dockerfile abaixo segue o que a doc do adapter-node pede pra rodar em produção: o diretório de saída, o package.json e as dependências de produção em node_modules

A ideia é um build multi-stage: um estágio compila, o outro só roda

# Estágio de build
FROM node:22 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Estágio final (Node 22.17 ou superior)
FROM node:22
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit dev
COPY --from=build /app/build ./build
EXPOSE 3000
CMD ["node", "build"]

O SvelteKit 3 exige Node 22.17 ou superior, então confira a versão da imagem com docker run --rm node:22 node --version antes de confiar na tag

Agora um compose simples, que publica a porta 3000 só no localhost do servidor, pra ela ser acessada pelo proxy e não direto pela internet:

services:
  app:
    build: .
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      PORT: "3000"

Se quiser outra porta, o servidor aceita HOST e PORT, como no exemplo da doc: HOST=127.0.0.1 PORT=4000 node build

Dentro do container, porém, o padrão 0.0.0.0 é o que deixa o Docker encaminhar a porta publicada, então mexa no HOST só se souber o motivo

E se usar SOCKET_PATH, o HOST e o PORT são ignorados

Pra quem já subiu algo parecido, a estrutura é a mesma de outros guias do blog, tipo o de Directus com Docker Compose, só que aqui com um único serviço

Na Hostinger, tem dois caminhos pra subir o compose:

  • Pelo painel: painel da VPS > Docker Manager > Compose, usando "Compose manually" ou "Compose from URL" (guia do Docker Manager). Se for usar esse caminho com um compose que faz build: ., confirme antes na documentação oficial se o Docker Manager builda a partir do código do projeto ou se espera uma imagem já publicada; na dúvida, o caminho mais direto pra build local é por SSH, como abaixo
  • Por SSH: no template Ubuntu 24.04, que já vem com docker-compose, rode dentro da pasta do projeto:
docker-compose up -d

Erro comum deste passo: copiar só a pasta build pra imagem e esquecer o node_modules de produção. O servidor até inicia o processo, mas quebra ao importar o primeiro pacote externo

Como configurar o SvelteKit atrás de proxy reverso (HTTPS e origem pública)

Isso importa por causa da origem

Sem paths.origin, o SvelteKit deriva a origem do cabeçalho host da requisição e usa o protocolo https por padrão, a menos que a variável PROTOCOL_HEADER esteja definida apontando pra outro cabeçalho. Essa origem entra na checagem de CSRF de formulários e remote functions

Por isso, se a origem derivada (ou configurada) não corresponder à URL pública real do app, essa checagem pode falhar: daí a importância de acertar paths.origin ou, quando fizer sentido, os cabeçalhos repassados pelo proxy

Tu tem duas saídas:

  • paths.origin no build: a preferível quando o domínio é fixo, porque não depende de nenhum cabeçalho ser repassado corretamente pelo proxy
  • Cabeçalhos do proxy: o adapter-node tem variáveis pra isso, incluindo a já citada PROTOCOL_HEADER. Pra host, porta, IP do cliente e limite de tamanho do corpo da requisição (upload), confira os nomes exatos, a semântica e os valores padrão direto na documentação oficial do adapter-node antes de aplicar, porque esses detalhes variam entre versões e prefiro não arriscar um nome ou valor errado aqui

Importante: cabeçalho HTTP pode ser manipulado por quem faz a requisição. Então qualquer variável que diga "leia o host, o protocolo ou o IP de tal cabeçalho" só é segura se existir, de fato, um proxy confiável na frente sobrescrevendo esse cabeçalho antes dele chegar no container. Se a porta do container estiver exposta direto pra internet, sem proxy nenhum, não configure esse tipo de variável

Erro comum deste passo: expor a porta do container direto na internet com variáveis de cabeçalho de proxy configuradas. Isso faz o servidor confiar em cabeçalhos que o próprio cliente pode estar enviando, então só configure elas atrás de um proxy que você controla

A configuração do proxy em si (Nginx, Caddy e etc.) foge do escopo deste guia, mas o raciocínio de container atrás de proxy com HTTPS aparece no guia de aplicação Bun com HTTPS, que serve de referência pro desenho geral

Como verificar se as rotas e formulários funcionam em produção

Com tudo no ar, a checagem é pelo domínio público, não pelo IP nem pelo localhost

  1. Página inicial e rota dinâmica: abra a home e uma rota com parâmetro (tipo uma página de post ou produto) e confira se as duas renderizam
  2. Asset estático: pegue um arquivo da pasta static e rode curl -I https://seudominio.com.br/arquivo.png. No adapter-node 6, estáticos são servidos só pra GET e HEAD e validados por ETag com hash de conteúdo, sem Last-Modified, então o esperado é ver o ETag na resposta
  3. Formulário POST: envie um formulário de verdade pelo navegador. Se passar, a checagem de CSRF aceitou a origem
  4. IP do cliente: se você configurou alguma variável de proxy pra leitura do IP, confira (num log ou rota de debug) se o IP lido é o do cliente e não o do proxy

Pra manutenção, vale guardar a expectativa: mudou o domínio ou adicionou um asset novo, o caminho mais seguro é gerar build e imagem novos, já que a lista de assets estáticos é registrada em tempo de build e paths.origin também é lido nesse momento

Na Hostinger, use o firewall do painel pra deixar abertas só as portas do proxy

Os backups semanais com restauração pelo painel ajudam, mas não substituem versionar o código e o arquivo do compose no Git

Erros comuns ao hospedar SvelteKit com adapter-node e como resolver

  • A variável ORIGIN não tem efeito > foi removida no adapter-node 6 > use paths.origin no vite.config e faça novo build > revise tutoriais antigos antes de copiar
  • Checagem de CSRF falha em formulário atrás de proxy > a origem derivada não bate com a URL pública > defina paths.origin (preferível) ou confira na documentação oficial do adapter-node quais cabeçalhos de proxy ajustar > teste um formulário a cada deploy
  • Configuração ignorada > ainda está no svelte.config.js > mova pra dentro do sveltekit() no vite.config > rode npx sv migrate sveltekit-3 e leia as pendências
  • Arquivo estático novo retorna 404 > foi adicionado depois do build > gere o build de novo > nunca edite a pasta de saída na mão
  • Upload grande rejeitado > o adapter-node limita por padrão o tamanho do corpo da requisição (variável BODY_SIZE_LIMIT) > confira o valor padrão atual e como ajustar na documentação oficial do adapter-node > defina o limite junto com a regra de negócio do upload
  • Falha ao importar pacote no container > o pacote está em dependencies (e não é biblioteca Svelte) e a imagem não rodou npm ci --omit dev > inclua a etapa no Dockerfile > confira o estágio final da imagem

Repara que nenhum desses erros se resolve trocando de servidor

São todos de configuração, então a correção é no projeto, no Dockerfile ou no compose 😉

Vídeo de apoio

No canal da Hora de Codar tem um vídeo sobre o Hermes Desktop App, um gerenciador remoto/local para o Hermes Agent, pra quem quiser conhecer essa ferramenta

Vídeo complementar do canal Hora de Codar, sobre um assunto relacionado.

Próximo passo: colocar o SvelteKit no ar

Com o projeto migrado pro SvelteKit 3 e o paths.origin apontando pro domínio certo, o próximo movimento é escolher o servidor

Dá pra conferir a VPS para este projeto, aplicar o template Docker com Ubuntu 24.04 e subir o compose pelo Docker Manager ou por SSH

Depois disso, falta só o proxy reverso com HTTPS na frente do container e a checagem das rotas pelo domínio público

Até o próximo post! 🙂

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.

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