O ChatCLI pode ser empacotado como container Docker e deployado no Kubernetes usando o Helm chart oficial. Esta página cobre todos os cenários de deployment.


Imagens Oficiais (GHCR)

As imagens Docker oficiais são publicadas automaticamente no GitHub Container Registry a cada release:

ImagemDescrição
ghcr.io/diillson/chatcli:latestServidor ChatCLI (gRPC)
ghcr.io/diillson/chatcli-operator:latestKubernetes Operator
  # Puxar a imagem do servidor
docker pull ghcr.io/diillson/chatcli:latest

# Ou uma versão específica
docker pull ghcr.io/diillson/chatcli:v1.57.0

# Puxar a imagem do operator
docker pull ghcr.io/diillson/chatcli-operator:latest
  

As imagens suportam multi-arch (linux/amd64 e linux/arm64).


Docker

Build da Imagem (Local)

  # Na raiz do projeto
docker build -t chatcli .
  

O Dockerfile usa multi-stage build para produzir uma imagem mínima (~20MB):

  • Build stage: golang:1.25-alpine compila o binário
  • Runtime stage: alpine:3.21 com usuário não-root, health check integrado

Build da Imagem do Operator (Local)

  # IMPORTANTE: deve ser construído a partir da raiz do repositório
# (go.mod do operator usa replace directive apontando para ../)
docker build -f operator/Dockerfile -t ghcr.io/diillson/chatcli-operator:latest .
  

O Dockerfile do operator usa:

  • Build stage: golang:1.25 com suporte multi-arch (TARGETARCH)
  • Runtime stage: gcr.io/distroless/static:nonroot (segurança máxima, sem shell)

Rodar com Docker

  # Modo mais simples
docker run -p 50051:50051 \
  -e LLM_PROVIDER=OPENAI \
  -e OPENAI_API_KEY=sk-xxx \
  chatcli

# Com autenticação
docker run -p 50051:50051 \
  -e CHATCLI_SERVER_TOKEN=meu-token \
  -e LLM_PROVIDER=CLAUDEAI \
  -e ANTHROPIC_API_KEY=sk-ant-xxx \
  chatcli

# Com volume para persistir sessões
docker run -p 50051:50051 \
  -v chatcli-sessions:/home/chatcli/.chatcli/sessions \
  -e LLM_PROVIDER=OPENAI \
  -e OPENAI_API_KEY=sk-xxx \
  chatcli
  

Docker Compose

O projeto inclui um docker-compose.yml pronto para desenvolvimento:

  # Defina as variáveis de ambiente
export LLM_PROVIDER=OPENAI
export OPENAI_API_KEY=sk-xxx

# Inicie
docker compose up -d

# Conecte do seu terminal
chatcli connect localhost:50051
  

O Docker Compose configura:

  • Porta 50051 exposta
  • Volumes persistentes para sessões e plugins
  • Restart automático (unless-stopped)
  • Todas as variáveis de LLM via environment
  • Hardening de segurança: filesystem read-only, no-new-privileges, limites de CPU/memória, tmpfs para /tmp

Arquivo docker-compose.yml

  version: "3.9"

services:
  chatcli-server:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: chatcli-server
    ports:
      - "50051:50051"
    environment:
      CHATCLI_SERVER_PORT: "50051"
      CHATCLI_SERVER_TOKEN: "${CHATCLI_SERVER_TOKEN:-}"
      LLM_PROVIDER: "${LLM_PROVIDER:-}"
      OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
      ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
      GOOGLEAI_API_KEY: "${GOOGLEAI_API_KEY:-}"
      OLLAMA_ENABLED: "${OLLAMA_ENABLED:-}"
      OLLAMA_BASE_URL: "${OLLAMA_BASE_URL:-}"
      LOG_LEVEL: "${LOG_LEVEL:-info}"
    volumes:
      - chatcli-sessions:/home/chatcli/.chatcli/sessions
      - chatcli-plugins:/home/chatcli/.chatcli/plugins
    restart: unless-stopped
    read_only: true
    tmpfs:
      - /tmp:size=100M
    security_opt:
      - no-new-privileges:true
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 1G

volumes:
  chatcli-sessions:
  chatcli-plugins:
  

O container roda com filesystem read-only e no-new-privileges por padrão. O diretório /tmp usa tmpfs em memória (limitado a 100MB). Os volumes nomeados (chatcli-sessions, chatcli-plugins) são os únicos pontos graváveis. Veja a documentação de segurança para detalhes.


Kubernetes (Helm)

O ChatCLI inclui um Helm chart completo em deploy/helm/chatcli/.

Pré-requisitos

  • Cluster Kubernetes (kind, minikube, EKS, GKE, AKS, etc.)
  • Helm 3.x instalado
  • kubectl configurado para o cluster

Instalação Básica

  # Instalação mínima
helm install chatcli deploy/helm/chatcli \
  --set llm.provider=OPENAI \
  --set secrets.openaiApiKey=sk-xxx

# Com autenticação
helm install chatcli deploy/helm/chatcli \
  --set llm.provider=CLAUDEAI \
  --set secrets.anthropicApiKey=sk-ant-xxx \
  --set server.token=meu-token-secreto
  

Instalação com K8s Watcher (Single-Target)

  helm install chatcli deploy/helm/chatcli \
  --set llm.provider=OPENAI \
  --set secrets.openaiApiKey=sk-xxx \
  --set watcher.enabled=true \
  --set watcher.deployment=myapp \
  --set watcher.namespace=production
  

Instalação com Multi-Target + Prometheus

Para monitorar múltiplos deployments com métricas Prometheus, use um values.yaml:

  # values-multi.yaml
llm:
  provider: CLAUDEAI
secrets:
  anthropicApiKey: sk-ant-xxx
watcher:
  enabled: true
  interval: "15s"
  maxContextChars: 32000
  targets:
    - deployment: api-gateway
      namespace: production
      metricsPort: 9090
      metricsFilter: ["http_requests_*", "http_request_duration_*"]
    - deployment: auth-service
      namespace: production
      metricsPort: 9090
    - deployment: worker
      namespace: batch
  
  helm install chatcli deploy/helm/chatcli -f values-multi.yaml
  

O chart automaticamente:

  • Cria ServiceAccount com RBAC para o watcher ler pods, eventos, logs
  • Auto-detecta multi-namespace: se targets estão em namespaces diferentes, usa ClusterRole em vez de Role
  • Gera ConfigMap <name>-watch-config com o YAML multi-target
  • Monta o config como volume e passa --watch-config ao container

Valores do Helm Chart

Servidor

ValorDescriçãoPadrão
replicaCountNúmero de réplicas1
image.repositoryRepositório da imagemghcr.io/diillson/chatcli
image.tagTag da imagemlatest
server.portPorta gRPC50051
server.metricsPortPorta HTTP para Prometheus metrics (0 = desabilitado)9090
server.tokenToken de autenticação""
serviceMonitor.enabledCriar ServiceMonitor (requer Prometheus Operator)false
serviceMonitor.intervalIntervalo de scrape do Prometheus30s

TLS

ValorDescriçãoPadrão
tls.enabledHabilitar TLSfalse
tls.certFileCaminho do certificado""
tls.keyFileCaminho da chave""
tls.existingSecretSecret existente com certs""

LLM

ValorDescriçãoPadrão
llm.providerProvedor padrão""
llm.modelModelo padrão""

Secrets (API Keys)

ValorDescrição
secrets.existingSecretSecret existente (em vez de criar um novo)
secrets.openaiApiKeyChave da OpenAI
secrets.anthropicApiKeyChave da Anthropic
secrets.googleaiApiKeyChave do Google AI
secrets.xaiApiKeyChave da xAI
secrets.stackspotClientIdStackSpot Client ID
secrets.stackspotClientKeyStackSpot Client Key
secrets.stackspotRealmStackSpot Realm
secrets.stackspotAgentIdStackSpot Agent ID

Ollama

ValorDescriçãoPadrão
ollama.enabledHabilitar Ollamafalse
ollama.baseUrlURL base do Ollamahttp://ollama:11434
ollama.modelModelo Ollama""

K8s Watcher

ValorDescriçãoPadrão
watcher.enabledHabilitar o watcherfalse
watcher.targetsLista de targets multi-deployment (ver abaixo)[]
watcher.deploymentDeployment único - legado""
watcher.namespaceNamespace do deployment - legado""
watcher.intervalIntervalo de coleta30s
watcher.windowJanela de observação2h
watcher.maxLogLinesLinhas de log por pod100
watcher.maxContextCharsBudget de contexto LLM32000

Campos de cada target (watcher.targets[].):

CampoDescriçãoObrigatório
deploymentNome do deploymentSim
namespaceNamespace (padrão: default)Não
metricsPortPorta Prometheus (0 = desabilitado)Não
metricsPathPath HTTP das métricasNão (/metrics)
metricsFilterFiltros glob para métricasNão

Persistência

ValorDescriçãoPadrão
persistence.enabledPersistir sessões em PVCtrue
persistence.storageClassStorage class""
persistence.sizeTamanho do volume1Gi

Segurança

ValorDescriçãoPadrão
podSecurityContext.runAsNonRootObriga execução como não-roottrue
podSecurityContext.runAsUserUID do processo1000
podSecurityContext.seccompProfile.typePerfil seccompRuntimeDefault
securityContext.allowPrivilegeEscalationPermite escalação de privilégiosfalse
securityContext.readOnlyRootFilesystemFilesystem somente-leituratrue
securityContext.capabilities.dropCapabilities removidasALL
rbac.clusterWideUsa ClusterRole em vez de Role namespace-scopedfalse

Quando readOnlyRootFilesystem está true, o chart monta automaticamente um tmpfs em /tmp e um emptyDir em /home/chatcli/.chatcli (200Mi) para dados de runtime. A variável HOME=/home/chatcli é definida automaticamente. Para monitorar múltiplos namespaces, habilite rbac.clusterWide: true. Veja a documentação de segurança para detalhes.

Nota: O ConfigMap e o Secret referenciados via envFrom são marcados como optional: true, permitindo criar o Instance/Deployment antes dos recursos dependentes. O operator observa Secrets automaticamente e dispara rolling updates quando são criados ou atualizados.

Rede

ValorDescriçãoPadrão
service.typeTipo do ServiceClusterIP
service.portPorta do Service50051
service.headlessHabilita Service headless para balanceamento gRPC client-side (recomendado quando replicaCount > 1)false
ingress.enabledHabilitar Ingressfalse

gRPC e múltiplas réplicas: O gRPC usa conexões HTTP/2 persistentes que fixam em um único pod. Para replicaCount > 1, habilite service.headless: true para ativar balanceamento round-robin via DNS. O client já possui keepalive e round-robin integrados.

Usando Secret Existente

Se você já tem um Secret com as API keys:

  helm install chatcli deploy/helm/chatcli \
  --set llm.provider=OPENAI \
  --set secrets.existingSecret=my-llm-keys
  

O Secret deve conter as chaves esperadas:

  apiVersion: v1
kind: Secret
metadata:
  name: my-llm-keys
type: Opaque
stringData:
  OPENAI_API_KEY: "sk-xxx"
  ANTHROPIC_API_KEY: "sk-ant-xxx"
  

Acessar o Servidor

Port Forward (Desenvolvimento)

  kubectl port-forward svc/chatcli 50051:50051
chatcli connect localhost:50051
  

NodePort

  helm install chatcli deploy/helm/chatcli \
  --set service.type=NodePort
chatcli connect <node-ip>:<node-port>
  

LoadBalancer

  helm install chatcli deploy/helm/chatcli \
  --set service.type=LoadBalancer

# Aguarde o IP externo
kubectl get svc chatcli -w
chatcli connect <external-ip>:50051
  

Ingress (com TLS)

  # values-prod.yaml
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: chatcli.meudominio.com
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls:
    - secretName: chatcli-tls
      hosts:
        - chatcli.meudominio.com
  
  helm install chatcli deploy/helm/chatcli -f values-prod.yaml
  

Upgrade e Rollback

  # Atualizar
helm upgrade chatcli deploy/helm/chatcli --set llm.model=gpt-4-turbo

# Rollback
helm rollback chatcli 1
  

Exemplo Completo: Produção

Single-Target (Legado)

  helm install chatcli deploy/helm/chatcli \
  --namespace chatcli --create-namespace \
  --set llm.provider=CLAUDEAI \
  --set secrets.anthropicApiKey=sk-ant-xxx \
  --set server.token=super-secret-token \
  --set tls.enabled=true \
  --set tls.existingSecret=chatcli-tls-certs \
  --set watcher.enabled=true \
  --set watcher.deployment=production-app \
  --set watcher.namespace=production \
  --set persistence.enabled=true \
  --set persistence.size=5Gi \
  --set resources.requests.memory=256Mi \
  --set resources.limits.memory=1Gi
  

Multi-Target com Prometheus (Recomendado)

  # values-prod.yaml
llm:
  provider: CLAUDEAI
secrets:
  existingSecret: chatcli-llm-keys
server:
  token: super-secret-token
tls:
  enabled: true
  existingSecret: chatcli-tls-certs
watcher:
  enabled: true
  interval: "15s"
  maxContextChars: 10000
  targets:
    - deployment: api-gateway
      namespace: production
      metricsPort: 9090
      metricsFilter: ["http_requests_*", "http_request_duration_*"]
    - deployment: auth-service
      namespace: production
      metricsPort: 9090
    - deployment: payment-service
      namespace: production
      metricsPort: 9090
      metricsFilter: ["payment_*", "stripe_*"]
    - deployment: worker
      namespace: batch
persistence:
  enabled: true
  size: 5Gi
resources:
  requests:
    memory: 256Mi
  limits:
    memory: 1Gi
  
  helm install chatcli deploy/helm/chatcli \
  --namespace chatcli --create-namespace \
  -f values-prod.yaml
  

Quando targets estão em namespaces diferentes (ex: production e batch), o chart cria automaticamente um ClusterRole em vez de Role namespace-scoped.


Próximo Passo