Instance e o pipeline de AIOps), veja K8s Operator.
Imagens
linux/amd64, linux/arm64), trazem atestados de SBOM e proveniência e são assinadas com cosign (keyless):
/usr/local/bin/chatcli (carimbado com a versão do release, que o GetServerInfo e o operator informam), /usr/local/bin/grpc-health-probe, ENTRYPOINT ["chatcli", "server"], EXPOSE 50051 e um HEALTHCHECK que roda grpc-health-probe -addr=:50051 em texto puro a cada 30 segundos. Argumentos depois do nome da imagem são flags do chatcli server. O Devin CLI não está na imagem, então DEVIN não é provedor do servidor.
Build local
--build-arg VERSION=… o binário informa a versão dev.
Docker
Fora do Kubernetes o servidor faz bind em127.0.0.1, o que dentro de um container significa que nada de fora o alcança. Publicar uma porta, portanto, exige CHATCLI_BIND_ADDRESS=0.0.0.0, e esse bind exige uma credencial.
Num container o servidor escreve o log no stderr em linhas JSON, então o
docker logs carrega os erros de inicialização e tudo o que vem depois sem configuração extra (CHATCLI_ENV=dev troca para o console colorido de desenvolvimento). Confira e conecte (o listener é texto puro, então o cliente precisa de CHATCLI_ALLOW_INSECURE=true):
CHATCLI_BIND_ADDRESS e as flags -p.
TLS no Docker
HEALTHCHECK embutido testa texto puro, então com TLS ele reporta unhealthy. A imagem não tem shell e o docker run --health-cmd sempre passa por um, então desligue (--no-healthcheck) ou use uma checagem em forma exec no Compose (abaixo). O certificado do servidor precisa de localhost nos SANs para o cliente acima; veja TLS para uma receita de certificado.
Docker Compose
Odocker-compose.yml do repositório faz o build da imagem a partir do código e roda endurecido (sistema de arquivos raiz somente leitura, no-new-privileges, tmpfs de 100 MB em /tmp, 2 CPUs / 1 GB). Ele faz bind em 0.0.0.0 e lê todas as variáveis de provedor do seu shell, então exporte uma credencial antes:
HOME=/home/nonroot e mantém o home inteiro num volume nomeado, chatcli-home, montado em /home/nonroot: o ~/.chatcli (sessões, o hub de conversas, memória, plugins, o arquivo de log) sobrevive a restarts, e o volume é gravável com a raiz somente leitura porque o Docker o inicializa com o dono do home da imagem. Coloque binários de plugin em ~/.chatcli/plugins dentro desse volume.
O HEALTHCHECK da imagem checa em texto puro. Com TLS no servidor, troque-o num override; o Compose mescla o docker-compose.override.yml automaticamente:
CHATCLI_FALLBACK_PROVIDERS liga a cadeia de fallback (não existe chave separada para ligar), e ela precisa listar o provedor primário primeiro. O compose também repassa do seu shell CHATCLI_FALLBACK_MAX_RETRIES (padrão 2), CHATCLI_FALLBACK_COOLDOWN_BASE (30s) e CHATCLI_FALLBACK_COOLDOWN_MAX (5m).
Kubernetes (Helm)
O chart do servidor é publicado como artefato OCI emoci://ghcr.io/diillson/charts/chatcli. A versão do chart e a da imagem do servidor são o mesmo número, e o appVersion do chart fixa a imagem.
Pré-requisitos
- Kubernetes 1.30+,
kubectlapontando para o cluster - Helm 3.8+ (suporte a OCI), Helm 4 incluído
- Uma chave de provedor LLM (ou IRSA / Workload Identity para Bedrock)
Instalação
1
Instale com uma credencial
0.0.0.0, então server.token (ou JWT / mTLS, abaixo) é obrigatório. O chart guarda o token no próprio Secret e o entrega como CHATCLI_SERVER_TOKEN via secretKeyRef, nunca como argumento de linha de comando. Num pod o servidor também escreve o log no stderr, então o kubectl logs o mostra.2
Espere subir
3
Conecte
chatcli gera <release>-chatcli (Deployment, Service e Secret). O helm install imprime os comandos exatos nas notas e avisa quando não há credencial.RuntimeDefault), um Service, um ConfigMap e um Secret carregados com envFrom, um ServiceAccount e RBAC para o watcher, um PVC de 1 Gi para sessões (persistence.enabled, ligado por padrão), os 17 CRDs de AIOps mais um hook pre-install/pre-upgrade que os reaplica (crdUpgrade.enabled) e, opcionalmente, Ingress, HPA, PDB, NetworkPolicy e ServiceMonitor.
Probes
Probegrpc do kubelet não faz TLS, então o chart usa:
Com
server.metricsPort: 0, startup e liveness caem para uma checagem TCP na porta gRPC. Esses probes funcionam com e sem TLS.
Credenciais
security.jwtIssuer / security.jwtAudience adicionam checagem de iss / aud; security.mtlsRole define a role de quem se identifica só por certificado (viewer/readonly, user/operator, admin; padrão user). Prefira os valores *Ref: os inline vão parar no ambiente do Deployment.
TLS
tls.existingSecret, certFile e keyFile assumem /etc/chatcli/tls/tls.crt e /etc/chatcli/tls/tls.key; defina-os só quando os arquivos do seu Secret tiverem outros nomes. Sem Secret, defina os dois caminhos (para arquivos que você monta com extraVolumes / extraVolumeMounts).
Para mTLS com uma CA de cliente no mesmo Secret use security.tlsClientCA: /etc/chatcli/tls/ca.crt; para um Secret de CA separado, monte-o com extraVolumes / extraVolumeMounts (exemplo em Valores de segurança). Pelo kubectl port-forward o certificado precisa de localhost nos SANs.
Referência de valores
Servidor
LLM
Com
secrets.existingSecret, crie o Secret com nomes de variáveis como chaves, a credencial inclusive:
Fallback de provedor
O chart coloca o
llm.provider (com llm.model) na frente, a menos que você mesmo o liste, porque o servidor monta a cadeia só a partir da lista e só a instala com dois ou mais provedores funcionando. Coloque a chave de cada provedor no Secret. O fallback.enabled só decide se o chart grava CHATCLI_FALLBACK_PROVIDERS; o chart não grava nenhuma variável separada para ligar, já que o servidor não lê nenhuma.
MCP
K8s watcher
Um alvo sem
kind é um Deployment; um alvo sem namespace observa o default. Alvos fora do namespace do release, ou em vários namespaces, mudam o RBAC do chart para ClusterRole automaticamente, e o mesmo vale para um watcher.namespace de alvo único diferente do namespace do release (vazio conta como default).
Armazenamento, memória, pipeline e recursos
O chart monta um emptyDir em
/home/chatcli/.chatcli e /tmp e define HOME=/home/chatcli, então a raiz somente leitura funciona.
O servidor lê os ConfigMaps de MCP, agents, skills e bootstrap só no startup. Cada um que o chart renderiza (a partir de mcp.servers ou *.definitions) entra no hash de uma anotação checksum/<nome> do pod, então um helm upgrade que o altera recria os pods. Um ConfigMap que você mesmo gerencia (*.existingConfigMap) não pode ser hasheado pelo Helm: depois de editar um, rode kubectl -n chatcli rollout restart deploy/chatcli. agents, skills ou bootstrap ligados sem definitions nem existingConfigMap não montam nada, e o servidor encontra um diretório vazio (charts anteriores montavam um ConfigMap que não existia, deixando o pod em ContainerCreating).
Rollouts no volume de sessões
Compersistence.enabled e sem o modo de acesso ReadWriteMany, um rollout para o pod antigo antes de subir o novo: o chart renderiza RollingUpdate com maxSurge: 0 e maxUnavailable: 1. Com o padrão do API server (um pod extra, nenhum indisponível), um pod novo agendado em outro nó não conseguia anexar o volume ReadWriteOnce enquanto o antigo o segurava, e o antigo só parava quando o novo ficava pronto, então o rollout travava. Parar o pod antigo primeiro troca isso por uma curta interrupção a cada rollout. Sem persistência, ou com ReadWriteMany, nada é renderizado e vale o padrão do API server.
Uma strategy explícita vence sempre; type: Recreate sem rollingUpdate é renderizado com rollingUpdate: null. Uma instalação nova, um release do Helm 3 e um release já atualizado uma vez no padrão do chart podem trocar para Recreate direto. Um release instalado pelo Helm 4 num chart anterior (até 1.211.x, que não renderizava estratégia) precisa antes de um upgrade no padrão, porque o server-side apply do Helm 4 não consegue remover o bloco rollingUpdate preenchido por padrão (o upgrade falha com spec.strategy.rollingUpdate: Forbidden), ou deste patch:
ReadWriteOnce anexa a um nó por vez, então várias réplicas nele só funcionam enquanto todas rodam nesse nó; pods agendados em outro lugar ficam em ContainerCreating. Rode uma réplica, ou use uma storage class ReadWriteMany para várias. ReadWriteOncePod admite um único pod: o render falha quando replicaCount (ou autoscaling.maxReplicas com o HPA ligado) passa de 1.
Memória no PVC de sessões
Commemory.enabled e persistência, as sessões ficam na raiz do PVC (montada em ~/.chatcli/sessions) e a memória fica no diretório memory.subPath do mesmo PVC (montado em ~/.chatcli/memory). O kubelet cria esse diretório na primeira montagem, e o podSecurityContext.fsGroup padrão o torna gravável. Charts anteriores (até 1.211.x) montavam a memória também na raiz do PVC, então os stores JSON dela ficavam entre os arquivos de sessão, onde a lista de sessões os mostrava e a expiração de sessões podia apagá-los.
O upgrade de uma instalação que rodava com memória e persistência migra sozinho. Nenhuma sessão muda de lugar. O chart define CHATCLI_MEMORY_LEGACY_DIR=/home/chatcli/.chatcli/sessions (a raiz do PVC como o servidor a vê) sempre que persistência, memória e um memory.subPath estão ligados, e na primeira vez que o servidor abre a memória no layout novo ele copia de lá os arquivos da própria memória para o diretório de memória:
- só os arquivos da memória (
MEMORY.mde seus backups,memory_index.json,memory_tombstones.json,episodes.json,user_profile.json,topics.json,projects.json,usage_stats.json,graph.json,vector_index.json,memory_archive.json,compactor_state.json, as quarentenas.corruptdeles, as notas diáriasYYYYMM/,weekly/,monthly/epending/); os arquivos de sessão ficam onde estão; - só cópia: nada na raiz é movido, apagado ou reescrito, e um arquivo que já existe no diretório de memória nunca é sobrescrito; cada arquivo chega de forma atômica com modo 0600;
- uma vez: roda só enquanto o diretório de memória não tem nenhum arquivo da memória, e grava o marcador
.migrated-from-legacylá ao terminar; - um arquivo ilegível é pulado com um aviso; um arquivo selado com
CHATCLI_ENCRYPTION_KEYé selado de novo para o caminho novo, e enquanto a chave falta ou está errada a cópia fica pendente (.migrating-from-legacy) e retoma no próximo start; - o log registra
memory: adopted the legacy memory directory.
memory.subPath: "" mantém o layout compartilhado antigo (sem cópia, sem CHATCLI_MEMORY_LEGACY_DIR). Um upgrade só com --reuse-values não traz a chave memory.subPath e também mantém o layout antigo; --reset-then-reuse-values pega o novo padrão.
Segurança
Service, ingress, network policy
Escala e monitoramento
O schema de valores rejeita chaves desconhecidas, então um valor com erro de digitação falha a instalação em vez de ser ignorado.
Expondo o servidor
className: nginx o chart define como padrão backend-protocol: GRPC (texto puro até o pod) e ssl-redirect: "true". Uma chave que você define em ingress.annotations substitui o padrão em vez de ser renderizada duas vezes, então para TLS de ponta a ponta pelo nginx (com tls.* no servidor) adicione nginx.ingress.kubernetes.io/backend-protocol: GRPCS.
Logs, health e métricas
kubectl logs e o seu coletor de logs leem; o arquivo rotativo em /home/chatcli/.chatcli/app.log (um emptyDir, sem shell para ler) é uma cópia que morre com o pod. CHATCLI_LOG_STDERR=false no extraEnv desliga a cópia no stderr.
Com a raiz somente leitura padrão, /home/chatcli/.chatcli é um emptyDir limitado a 200Mi, e um volume acima do limite faz o pod ser despejado. Os padrões de rotação do próprio servidor (100 MB, 3 backups: até 400 MB) não cabem, então o chart define uma rotação que cabe, no máximo 80 MB de log:
Enquanto esse emptyDir está em uso, o render falha quando
maxSizeMB × (maxBackups + 1) passa de 100 MB. Um campo definido como null não renderiza variável (vale o padrão do servidor, que conta como tal na checagem). Uma entrada de mesmo nome no extraEnv vence o valor do chart, e uma para o tamanho ou os backups pula a checagem.
- Uma mudança no Secret ou ConfigMap gerenciado pelo chart recria os pods (anotações de checksum), incluindo os ConfigMaps de MCP, agents, skills e bootstrap. Uma mudança no
secrets.existingSecretou num*.existingConfigMapnão: rodekubectl -n chatcli rollout restart deploy/chatcli. - Com persistência num volume sem
ReadWriteMany, cada rollout para o pod antigo antes de subir o novo: espere uma curta interrupção (veja Rollouts no volume de sessões). - Se o chart do servidor e o chart
chatcli-operatorestiverem instalados, mantenha os dois na mesma versão: cada um reaplica a própria cópia dos CRDs. - O
helm uninstallapaga o PVC de sessões; faça backup antes. Os CRDs ficam; apagá-los apaga todos os recursos daqueles tipos.
Solução de problemas
Veja também a tabela de problemas do servidor.
Próximos passos
Modo Servidor
Flags, auth, limites, operação
Conexão Remota
Conecte ao servidor
K8s Watcher
Monitore workloads