Blog
Tutoriais

Implantar o Simmetric Chat com Docker: guia completo

O mapa completo da implantação do Simmetric Chat - um stack Compose de seis serviços, um contêiner all-in-one único para hosts air-gapped e o caminho Coolify em um clique.

Simmetric Team7 min de leitura10/03/2026

Um produto, três formas de implantação

O Simmetric Chat é uma plataforma multisserviço - um servidor de API, um collector para processamento de documentos, um serviço de widget embutível, um frontend e um banco PostgreSQL - então a história da implantação é escolher o empacotamento certo para o seu ambiente, não cabear serviços à mão. Existem três caminhos de produção, e todos os artefatos vivem no diretório docker/ do repositório:

  • Compose multicontêiner - seis serviços, cada um em seu próprio contêiner com healthcheck. O layout de produção recomendado, com isolamento por serviço e reinícios independentes.
  • Contêiner único all-in-one - uma única imagem que executa tudo: Postgres, servidor, collector e widget supervisionados pelo supervisord. Pensada para hosts air-gapped e máquinas de demonstração, onde mover várias imagens pelo perímetro é a parte cara.
  • Coolify - um caminho PaaS self-hosted com terminação TLS do Traefik, em que a plataforma é clonada e compilada para você a partir de um arquivo Compose ajustado às convenções do Coolify.

Todas as imagens são builds multi-stage sobre bases Node 24, e cada serviço expõe um healthcheck do qual depende a ordem de inicialização da pilha. Este artigo percorre cada caminho, o modelo de configuração e os detalhes que mordem em produção.

O stack Compose de seis serviços

O arquivo Compose de produção define seis serviços ativos:

  • frontend - compilado como app React 19 a partir de um builder Node, depois servido pelo nginx, que termina TLS com certificados autoassinados gerados no build da imagem, serve a SPA e faz proxy de /api e /widget para os serviços de backend.
  • server - a API Express 5: autenticação, RBAC, orquestração de chats e o agente ReAct. É o serviço com o qual seus usuários falam.
  • collector - o pipeline de documentos: parsing, chunking e embedding dos uploads. Compilado sobre uma base Node slim em vez de Alpine, porque suas dependências nativas (LanceDB, os transformers de embedding locais, os bindings de canvas) exigem glibc.
  • widget - o serviço do widget de chat embutível para sites voltados ao público externo.
  • postgres - PostgreSQL 16 com a extensão pgvector incluída, para que o provider vetorial padrão não exija um serviço de banco vetorial separado.
  • redis - o cache opcional de escalabilidade, habilitado com persistência append-only e eviction LRU. Cada consumidor de Redis degrada graciosamente em memória, então este serviço pode estar dentro ou fora sem problemas.

Dois serviços estão deliberadamente desabilitados no arquivo padrão em vez de ausentes: ollama e qdrant. Seus blocos vêm comentados, e habilitá-los é descomentar uma linha mais uma variável - OLLAMA_BASE_URL=http://ollama:11434 para o host dos modelos locais, ou VECTOR_DB_PROVIDER=qdrant se você quiser um armazenamento vetorial remoto em vez do LanceDB em arquivo padrão. Os padrões comentados mantêm uma instalação mínima realmente mínima.

A ordem de inicialização é governada pelos healthchecks: sondas baseadas em wget atingem GET /api/health no servidor e no collector, /health no widget, pg_isready no Postgres e redis-cli ping no Redis, e o frontend espera o servidor se declarar healthy antes de iniciar.

Subir o stack é um único comando:

bash
docker compose -f docker/docker-compose.yml up --build -d

O contêiner único all-in-one

O segundo caminho comprime toda a plataforma em uma única imagem. Um único Dockerfile compila o PostgreSQL 16 com a extensão pgvector compilada a partir do código-fonte no build da imagem, e o supervisord executa juntos, dentro do contêiner, banco de dados, servidor, collector e widget. As portas 3000, 3210 e 3211 estão expostas, e o healthcheck consulta o /api/health do servidor em localhost.

bash
docker build -f docker/Dockerfile -t simmetric-chat-all-in-one .
docker run -d -p 3000:3000 -p 3210:3210 -p 3211:3211 simmetric-chat-all-in-one

A imagem all-in-one é a história do air-gap: um único artefato para atravessar o perímetro, nenhum pull de registry no momento da instalação, e tudo - do banco de dados à UI de chat - iniciando com um único docker run. As fontes do pgvector são compiladas e instaladas offline durante o build justamente para que a imagem resultante não tenha dependências de rede na instalação. Seus valores de ambiente padrão são placeholders de andaime por design - o operador sobrescreve os segredos reais via .env ou -e, e a imagem nunca finge ser production-safe recém-saída da caixa.

Há uma fronteira que vale a pena conhecer: o plugin enterprise nunca é embutido em nenhuma imagem. É deliberado - mantém as imagens AGPL da comunidade livres de código proprietário e permite que clientes air-gapped recebam o plugin como tarball separado e verificável, montado por bind em tempo de execução.

O caminho Coolify

O Coolify transforma o stack em uma implantação PaaS: clona o repositório, compila as imagens, cabeia a rede interna e coloca o Traefik na frente para a terminação TLS. Um arquivo Compose dedicado automatiza o que o Compose padrão deixa para o operador - builds de imagens, networking interno, volumes, healthchecks e a montagem do plugin enterprise.

Ele também aplica os segredos requeridos no momento do deploy com a sintaxe :?: JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL e ALLOWED_ORIGINS. O Coolify se recusa a implantar quando falta um deles, o que transforma uma classe de implantações mal configuradas em um erro visível antes que qualquer coisa inicie.

As diferenças em relação ao stack padrão decorrem da presença do Traefik: os serviços de aplicação não publicam portas no host (atribua o domínio ao serviço frontend, porta 80), a configuração do nginx é somente HTTP porque o Traefik termina o TLS, e o socket Docker não é montado por padrão - ele só serve ao fluxo de login do Ollama Cloud. Um runbook passo a passo cobre os passos manuais: rsync da árvore enterprise, preenchimento dos segredos e inserção da chave de licença.

Configuração: um único .env, montado pelo Compose

Os contêineres recebem a configuração via env_file do Compose, que aponta para o .env na raiz do repositório - o mesmo arquivo único de configuração de runtime que a plataforma usa em todos os lugares. O stack inicia apenas com os padrões em nível de compose se o arquivo estiver ausente: suficiente para um primeiro smoke test, não para produção.

Dois segredos são estritamente requeridos, e o servidor sai sem eles:

env
# .env na raiz - requeridos em produção
JWT_SECRET=...          # openssl rand -hex 32
COLLECTOR_SECRET=...    # openssl rand -hex 32

ENCRYPTION_KEY e API_KEY_HMAC_SECRET não precisam de forma alguma ser pré-provisionados: nas inicializações de produção, o entrypoint do servidor os gera uma vez, os persiste no volume server-storage, os restaura a cada reinício e prefere um valor fornecido pelo operador quando existe. O provisioning nunca regenera sobre material de chave existente e falha em voz alta antes de qualquer passo no banco de dados, para que um volume meio escrito não possa se tornar silenciosamente uma chave diferente.

Há uma armadilha específica do Compose que vale a pena nomear porque degrada implantações em silêncio: não adicione entradas de passthrough como LICENSE_KEY=${LICENSE_KEY:-} aos serviços de produção. A interpolação do shell resolve uma variável não definida para a string vazia, e uma entrada environment explicitamente vazia sobrescreve o valor que chega via env_file - então um LICENSE_KEY interpolado vazio transforma uma implantação Enterprise em Community, e um ENCRYPTION_KEY interpolado vazio sobrescreve o auto-provisioning do entrypoint. Deixe essas chaves apenas para o env_file.

Os detalhes que importam em produção

  • O collector exige glibc. O collector roda sobre uma base slim (Debian), não Alpine, porque LanceDB, os transformers de embedding locais e o canvas exigem todos glibc. Não "simplifique" para Alpine - o build falha.
  • As URLs de banco têm três formas. Dentro da rede Compose o servidor fala com postgres:5432 (cabeado no environment do compose); o padrão do .env da raiz aponta para host.docker.internal para servidores no lado do host; a imagem all-in-one usa localhost porque o Postgres roda no mesmo contêiner.
  • As migrações são apenas aditivas. O entrypoint do servidor executa prisma migrate deploy a cada inicialização, então rolar as imagens para frente é seguro por política; voltar atrás significa reimplantar uma imagem conhecida como boa, e o rollback de código não restaura as migrações aplicadas.
  • A escalabilidade espera o provisioning. O auto-provisioning dos segredos precisa se completar uma vez antes que o servidor escale além de uma réplica - réplicas concorrentes na primeira inicialização poderiam gerar chaves divergentes. A instância única é o padrão documentado; a escalabilidade multi-instância tem seu próprio guia dedicado.
  • Os endpoints de saúde são externos. /api/health retorna um objeto de status com verificações por dependência e serve para sondas de uptime externas e verificações de load balancers; os logs estruturados vão para stdout para docker compose logs -f <serviço>.

As imagens publicadas são publicadas no GHCR (server, frontend, collector, widget e all-in-one, marcadas com latest mais a versão), então uma implantação pode fixar uma versão em vez de compilar localmente - embora docker compose up --build a partir do repositório continue sendo o caminho totalmente self-hosted.

Do clone à plataforma em execução

O caminho mais curto, de ponta a ponta:

  1. Clone o repositório e copie .env.example para .env.
  2. Defina JWT_SECRET e COLLECTOR_SECRET (o entrypoint provisiona por conta própria as chaves de criptografia).
  3. docker compose -f docker/docker-compose.yml up --build -d.
  4. Acompanhe docker compose logs -f server até migrações e seeding terminarem, depois abra o frontend e entre.

Para hosts air-gapped, substitua pela imagem all-in-one e pelo fluxo de transferência por tarball. Para o Coolify, importe o repositório e preencha os segredos requeridos - a plataforma faz o resto da implantação.

Leia o guia completo na documentação →

Artigos relacionados