Kubo
Montar stack
Guias

Deploy com Docker Compose

Aprenda a fazer self-host do app kubojs com os Dockerfiles e docker-compose.yml gerados

byAman Varshney·

Visão geral

Este guia explica como o kubojs empacota suas aplicações para self-hosting com Docker Compose. Você vai aprender:

  • O que é gerado e como a stack do compose é ligada
  • Como buildar, rodar e monitorar os containers
  • Como as variáveis de ambiente fluem entre builds, containers e o arquivo compose
  • Como funciona o serviço opcional de banco Docker
  • O que mudar ao sair de containers locais para um host real

O que é gerado

Escolher docker como alvo de deploy gera:

docker-compose.yml
.dockerignore
  • docker-compose.yml na raiz do repo define os serviços web, server e (opcionalmente) de banco, com health checks e ordem de startup.
  • apps/*/Dockerfile são builds multi-stage que instalam o workspace monorepo inteiro, buildam um app e embarcam uma imagem de runtime mínima.
  • nginx.conf é gerado para frontends SPA estáticos (TanStack Router, SolidJS), servidos por nginx em vez de um server Node.

Habilitando deploy Docker

Deploy combinado (web + server + banco):

npm create kubojs@latest my-app \
  --frontend tanstack-router \
  --backend hono \
  --runtime bun \
  --database postgres \
  --db-setup docker \
  --web-deploy docker \
  --server-deploy docker

Só web (backend fullstack self):

npm create kubojs@latest my-app \
  --frontend next \
  --backend self \
  --web-deploy docker

Só server:

npm create kubojs@latest my-app \
  --frontend none \
  --backend fastify \
  --runtime node \
  --server-deploy docker

O deploy de server exige runtime bun ou node. Adicionar --db-setup docker coloca um container Postgres, MySQL ou MongoDB no mesmo arquivo compose, com volume nomeado e health check.

Rodando a stack

bun docker:build   # docker compose build
bun docker:up      # docker compose up -d --build
bun docker:logs    # docker compose logs -f
bun docker:down    # docker compose down

Depois de docker:up:

ServiçoURLNotas
webhttp://localhost:3001nginx (frontends SPA) ou o server do framework
serverhttp://localhost:3000com health check; o web espera ele ficar healthy
databaselocalhost:5432 / 3306 / 27017somente com --db-setup docker

A ordem de startup usa depends_on + condition: service_healthy: o banco precisa passar no health check antes do server iniciar, e o server precisa passar no health check fetch('http://localhost:3000/') antes do container web iniciar.

Com --db-setup docker você também ganha scripts de banco com escopo para desenvolvimento local sem a stack completa:

bun db:start   # docker compose up -d postgres   (just the database, detached)
bun db:watch   # docker compose up postgres      (with logs in the foreground)

Como as imagens são buildadas

Cada Dockerfile é um build multi-stage com a raiz do monorepo como contexto de build:

  1. Um stage base instala o workspace com seu gerenciador de pacotes (com cache de dependências montado, para rebuilds rápidos).
  2. O app é buildado com SKIP_ENV_VALIDATION=1, para o t3-env não exigir secrets reais no build da imagem. Projetos Prisma recebem um DATABASE_URL placeholder para prisma generate; o valor real chega em runtime pelo compose.
  3. Um stage de runtime enxuto copia só a saída buildada. Frontends SPA vão para uma imagem nginx; frontends SSR e servers rodam em node:24-slim (com o binário Bun adicionado quando necessário).

Variáveis de ambiente

Três regras cobrem tudo:

  1. Variáveis públicas do web são embutidas no build. Valores como VITE_SERVER_URL são build args do compose — ficam inline no bundle do client quando a imagem é buildada. O padrão gerado é http://localhost:3000 (a porta publicada do server). Mudá-los exige rebuild da imagem, não só restart.
  2. Variáveis de runtime vêm do arquivo .env de cada app. O compose carrega apps/web/.env e apps/server/.env via env_file (marcados como opcionais, para arquivos ausentes não quebrarem o startup).
  3. Overrides do compose cuidam do networking dos containers. Dentro da rede Docker, containers se alcançam pelo nome do serviço, não por localhost — então o compose define valores como DATABASE_URL: postgresql://postgres:...@postgres:5432/my-app e CORS_ORIGIN: http://localhost:3001 no bloco environment:, que prevalece sobre env_file. Seus .env locais continuam apontando para localhost no desenvolvimento fora do Docker.

Senhas de banco têm padrão password e podem ser sobrescritas com variáveis do compose (POSTGRES_PASSWORD, MYSQL_PASSWORD, MONGO_PASSWORD) — defina-as em um .env na raiz ou exporte antes de docker:up.

Indo para um host real

O compose gerado já tem forma de produção (imagens multi-stage, health checks, restart: unless-stopped, volumes nomeados), mas algumas coisas ainda estão dimensionadas para localhost:

  • Coloque um reverse proxy na frente. Termine TLS com Caddy, Traefik ou nginx e roteie seu domínio para o container web; pare de publicar as portas do server e do banco publicamente quando o proxy cuidar do roteamento.
  • Atualize URLs embutidas para o seu domínio. Rebuild a imagem web com o build arg da URL pública do server apontando para a origem real da API, e defina CORS_ORIGIN / BETTER_AUTH_URL no server com a origem real do web.
  • Defina secrets reais. Substitua a senha padrão do banco e coloque valores de produção no ambiente do server (environment: do compose, um .env no host ou o secret store do seu orquestrador).
  • Faça backup do volume do banco. Os dados ficam em um volume Docker nomeado (<project>_postgres_data etc.); docker compose down -v apaga.

Troubleshooting

Porta já em uso

A stack publica 3001 (web), 3000 (server) e a porta do banco. Pare o que estiver usando essas portas ou edite os mapeamentos ports: em docker-compose.yml.

Mudei uma env pública mas o app web não pegou

Variáveis públicas são build args embutidos no bundle do client. Rebuild a imagem: bun docker:up (passa --build) ou bun docker:build antes.

O server não alcança o banco com URL localhost

Dentro do compose, o host do banco é o nome do serviço (postgres, mysql, mongodb), não localhost. Use o DATABASE_URL fornecido pelo compose (já definido no bloco environment:) em vez do de apps/server/.env.

O container web nunca inicia

Ele espera o health check do server. Veja bun docker:logs — se o server estiver em crash-loop (em geral env ausente ou DATABASE_URL ruim), o container web fica em estado waiting.

Erros de CORS no browser

O compose define CORS_ORIGIN: http://localhost:3001 quando o serviço web está presente. Se você acessar o app de outro host ou porta, atualize esse valor para a origem que está usando de fato e reinicie.