Armazenamento de objetos

O Zoen armazena todo arquivo enviado — documentos de bases de conhecimento, anexos de chat, saídas de execução, fotos de perfil e mais — em object storage. Quatro backends são suportados:

BackendQuando usar
Disco localDocker single-node, desenvolvimento local, avaliação
AWS S3Produção, especialmente com mais de uma réplica do app
Azure BlobProdução no Azure
Google Cloud StorageProdução no GCP

O disco local grava no diretório /uploads do container. Os arquivos são perdidos quando o container é recriado, a menos que esse caminho esteja em um volume persistente, e não são compartilhados entre réplicas. Em qualquer deployment multi-réplica ou de produção, use S3, Azure Blob ou Google Cloud Storage.

Como o backend é selecionado

O Zoen escolhe o backend automaticamente a partir das variáveis de ambiente — não há uma flag explícita de "provider". A lógica, em ordem de precedência:

  1. Azure Blob — usado se AZURE_STORAGE_CONTAINER_NAME estiver definido e (AZURE_ACCOUNT_NAME + AZURE_ACCOUNT_KEY) ou AZURE_CONNECTION_STRING estiver definido.
  2. AWS S3 — usado se S3_BUCKET_NAME e AWS_REGION estiverem definidos (e o Azure não estiver configurado).
  3. Google Cloud Storage — usado se GCS_BUCKET_NAME estiver definido (e nem Azure nem S3 estiverem configurados).
  4. Disco local — o fallback quando nenhum está configurado.

Se mais de um backend estiver configurado, o primeiro match nessa ordem vence. Defina só as variáveis do backend que você pretende usar.

Configurar AWS S3

Crie os buckets

O Zoen separa arquivos em buckets por finalidade. No mínimo você precisa do bucket geral do workspace; o restante é criado sob demanda conforme as env vars que você definir. Um bucket que não estiver configurado faz fallback para o bucket geral onde o código permite, mas o setup recomendado é um bucket por finalidade.

# Defina a região uma vez
export AWS_REGION=us-east-1

# Crie os buckets (nomes devem ser globalmente únicos — prefixe com sua org)
for name in workspace-files knowledge-base execution-files chat-files \
            copilot-files profile-pictures og-images workspace-logos; do
  aws s3api create-bucket \
    --bucket "myorg-sim-$name" \
    --region "$AWS_REGION" \
    --create-bucket-configuration LocationConstraint="$AWS_REGION"
done

Em us-east-1, omita a flag --create-bucket-configuration — essa região rejeita um LocationConstraint explícito.

Mantenha todos os buckets privados (bloqueie acesso público). O Zoen serve arquivos por URLs pré-assinadas de curta duração, então os buckets nunca precisam de leitura pública.

Conceda acesso com uma política IAM

Crie uma política IAM com escopo nos seus buckets e anexe-a ao usuário (ou role) sob o qual o Zoen roda:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::myorg-sim-*",
        "arn:aws:s3:::myorg-sim-*/*"
      ]
    }
  ]
}

Você tem duas formas de fornecer credenciais:

  • Chaves estáticas — crie um usuário IAM com essa política e defina AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY.
  • Credenciais de instância/role (recomendado) — anexe a política à role da instância EC2, task role do ECS ou role IRSA do EKS. Deixe AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY sem definir e o Zoen cai automaticamente na cadeia padrão de credenciais AWS.

Configure as variáveis de ambiente

Defina a região, opcionalmente as credenciais, e os nomes dos buckets:

# Região + credenciais
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIA...          # omita ao usar role de instância/IRSA
AWS_SECRET_ACCESS_KEY=...          # omita ao usar role de instância/IRSA

# Buckets (por finalidade)
S3_BUCKET_NAME=myorg-sim-workspace-files
S3_KB_BUCKET_NAME=myorg-sim-knowledge-base
S3_EXECUTION_FILES_BUCKET_NAME=myorg-sim-execution-files
S3_CHAT_BUCKET_NAME=myorg-sim-chat-files
S3_COPILOT_BUCKET_NAME=myorg-sim-copilot-files
S3_PROFILE_PICTURES_BUCKET_NAME=myorg-sim-profile-pictures
S3_OG_IMAGES_BUCKET_NAME=myorg-sim-og-images
S3_WORKSPACE_LOGOS_BUCKET_NAME=myorg-sim-workspace-logos

AWS_REGION e S3_BUCKET_NAME são estritamente obrigatórios para colocar o Zoen em modo S3. Adicione as demais para que cada tipo de arquivo caia no próprio bucket.

Referência de buckets S3

VariávelArmazenaObrigatório
AWS_REGIONRegião de todos os bucketsSim (ativa o S3)
AWS_ACCESS_KEY_IDAccess keyNão (usa a cadeia de credenciais se não definido)
AWS_SECRET_ACCESS_KEYSecret keyNão (usa a cadeia de credenciais se não definido)
S3_BUCKET_NAMEArquivos gerais do workspaceSim (ativa o S3)
S3_KB_BUCKET_NAMEDocumentos de bases de conhecimentoRecomendado
S3_EXECUTION_FILES_BUCKET_NAMEArquivos de execução de workflows (padrão: sim-execution-files)Recomendado
S3_CHAT_BUCKET_NAMEAssets de chats publicadosRecomendado
S3_COPILOT_BUCKET_NAMEAnexos do CopilotRecomendado
S3_PROFILE_PICTURES_BUCKET_NAMEAvatares de usuárioRecomendado
S3_OG_IMAGES_BUCKET_NAMEImagens de preview OpenGraph (fallback para S3_BUCKET_NAME)Opcional
S3_WORKSPACE_LOGOS_BUCKET_NAMELogos de workspace (fallback para S3_BUCKET_NAME)Opcional
S3_LOGS_BUCKET_NAMELogs armazenadosOpcional
S3_ENDPOINTEndpoint customizado para storage compatível com S3 (R2, MinIO, B2)Opcional (AWS S3 se não definido)
S3_FORCE_PATH_STYLEtrue para addressing path-style (MinIO/Ceph)Opcional (padrão false)

Aplicar a configuração

Adicione as variáveis de storage ao arquivo .env usado por docker-compose.prod.yml e reinicie:

docker compose -f docker-compose.prod.yml up -d

Como os arquivos agora ficam no S3, você não depende mais de um volume local /uploads para durabilidade.

Defina as variáveis em app.env (não secretas, ex.: região e nomes de buckets) e forneça as credenciais por um secret. O chart traz um exemplo completo em helm/sim/examples/values-aws.yaml:

app:
  env:
    AWS_REGION: "us-east-1"
    S3_BUCKET_NAME: "myorg-sim-workspace-files"
    S3_KB_BUCKET_NAME: "myorg-sim-knowledge-base"
    S3_EXECUTION_FILES_BUCKET_NAME: "myorg-sim-execution-files"
    # ...buckets restantes

No EKS, prefira IRSA: anexe a política IAM à role da service account e deixe as variáveis de access key sem definir.

Configurar Azure Blob

O Azure Blob usa um container por finalidade, espelhando o layout do S3. Autentique com uma connection string ou com account name + key.

# Credenciais — forneça UMA dessas formas
AZURE_ACCOUNT_NAME=mystorageaccount
AZURE_ACCOUNT_KEY=...
# ou
AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net

# Containers (por finalidade)
AZURE_STORAGE_CONTAINER_NAME=workspace-files
AZURE_STORAGE_KB_CONTAINER_NAME=knowledge-base
AZURE_STORAGE_EXECUTION_FILES_CONTAINER_NAME=execution-files
AZURE_STORAGE_CHAT_CONTAINER_NAME=chat-files
AZURE_STORAGE_COPILOT_CONTAINER_NAME=copilot-files
AZURE_STORAGE_PROFILE_PICTURES_CONTAINER_NAME=profile-pictures
AZURE_STORAGE_OG_IMAGES_CONTAINER_NAME=og-images
AZURE_STORAGE_WORKSPACE_LOGOS_CONTAINER_NAME=workspace-logos

Um exemplo completo de Helm está em helm/sim/examples/values-azure.yaml.

Configurar Google Cloud Storage

Crie os buckets

O GCS usa um bucket por finalidade, espelhando o layout do S3:

export PROJECT_ID=your-project-id
export LOCATION=us-central1

# Crie os buckets (nomes devem ser globalmente únicos — prefixe com sua org)
for name in workspace-files knowledge-base execution-files chat-files \
            copilot-files profile-pictures og-images workspace-logos; do
  gcloud storage buckets create "gs://myorg-sim-$name" \
    --project "$PROJECT_ID" \
    --location "$LOCATION" \
    --uniform-bucket-level-access
done

Mantenha todos os buckets privados (sem bindings allUsers). O Zoen serve arquivos por V4 signed URLs de curta duração, então os buckets nunca precisam de leitura pública.

Como os uploads são enviados diretamente do navegador via requisições PUT assinadas, cada bucket precisa de uma política CORS que permita a origem do seu Zoen:

cat > /tmp/cors.json <<'EOF'
[
  {
    "origin": ["https://your-sim-domain.com"],
    "method": ["GET", "PUT"],
    "responseHeader": [
      "Content-Type",
      "ETag",
      "x-goog-meta-originalname",
      "x-goog-meta-uploadedat",
      "x-goog-meta-purpose",
      "x-goog-meta-userid",
      "x-goog-meta-workspaceid",
      "x-goog-meta-workflowid",
      "x-goog-meta-executionid"
    ],
    "maxAgeSeconds": 3600
  }
]
EOF

for name in workspace-files knowledge-base execution-files chat-files \
            copilot-files profile-pictures og-images workspace-logos; do
  gcloud storage buckets update "gs://myorg-sim-$name" --cors-file=/tmp/cors.json
done

Os nomes de headers precisam ser listados individualmente — o CORS do GCS faz match exato das entradas de responseHeader e não aceita wildcards como x-goog-meta-*. ETag é obrigatório porque uploads multipart de arquivos grandes leem o ETag de cada parte no navegador, e o CORS esconde o header caso contrário.

Conceda acesso

Crie uma service account (ou reutilize a que o workload usa) e conceda acesso a objetos nos buckets:

gcloud iam service-accounts create sim-storage --project "$PROJECT_ID"

for name in workspace-files knowledge-base execution-files chat-files \
            copilot-files profile-pictures og-images workspace-logos; do
  gcloud storage buckets add-iam-policy-binding "gs://myorg-sim-$name" \
    --member "serviceAccount:sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \
    --role roles/storage.objectAdmin
done

Você tem duas formas de fornecer credenciais:

  • Application Default Credentials (recomendado no GCP) — rode o Zoen com a service account via GKE Workload Identity (ou anexe-a à instância GCE) e deixe GCS_CREDENTIALS_JSON sem definir. Como não há chave privada nesse modo, a geração de signed URLs usa a API IAM signBlob — conceda à service account roles/iam.serviceAccountTokenCreator em si mesma:

    gcloud iam service-accounts add-iam-policy-binding \
      "sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \
      --member "serviceAccount:sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \
      --role roles/iam.serviceAccountTokenCreator
  • Chave inline (para Docker Compose ou hosts fora do GCP) — crie uma chave JSON da service account e defina GCS_CREDENTIALS_JSON com o conteúdo. Com uma chave privada presente, as signed URLs são geradas localmente e nenhuma role IAM extra é necessária.

Configure as variáveis de ambiente

# Credenciais — omita ambas ao usar Workload Identity / ADC
GCS_PROJECT_ID=your-project-id            # opcional; inferido das credenciais quando não definido
GCS_CREDENTIALS_JSON='{"type":"service_account","client_email":"...","private_key":"..."}'

# Buckets (por finalidade)
GCS_BUCKET_NAME=myorg-sim-workspace-files
GCS_KB_BUCKET_NAME=myorg-sim-knowledge-base
GCS_EXECUTION_FILES_BUCKET_NAME=myorg-sim-execution-files
GCS_CHAT_BUCKET_NAME=myorg-sim-chat-files
GCS_COPILOT_BUCKET_NAME=myorg-sim-copilot-files
GCS_PROFILE_PICTURES_BUCKET_NAME=myorg-sim-profile-pictures
GCS_OG_IMAGES_BUCKET_NAME=myorg-sim-og-images
GCS_WORKSPACE_LOGOS_BUCKET_NAME=myorg-sim-workspace-logos

GCS_BUCKET_NAME é estritamente obrigatório para colocar o Zoen em modo GCS. Todo bucket por finalidade faz fallback para o bucket geral quando não definido — adicione os demais para que cada tipo de arquivo caia no próprio bucket.

Referência de buckets GCS

VariávelArmazenaObrigatório
GCS_BUCKET_NAMEArquivos gerais do workspaceSim (ativa o GCS)
GCS_PROJECT_IDID do projeto GCPNão (inferido das credenciais/ADC)
GCS_CREDENTIALS_JSONJSON inline da service accountNão (usa Application Default Credentials se não definido)
GCS_KB_BUCKET_NAMEDocumentos de bases de conhecimentoRecomendado (fallback para GCS_BUCKET_NAME)
GCS_EXECUTION_FILES_BUCKET_NAMEArquivos de execução de workflowsRecomendado (fallback para GCS_BUCKET_NAME)
GCS_CHAT_BUCKET_NAMEAssets de chats publicadosRecomendado (fallback para GCS_BUCKET_NAME)
GCS_COPILOT_BUCKET_NAMEAnexos do CopilotRecomendado (fallback para GCS_BUCKET_NAME)
GCS_PROFILE_PICTURES_BUCKET_NAMEAvatares de usuárioRecomendado (fallback para GCS_BUCKET_NAME)
GCS_OG_IMAGES_BUCKET_NAMEImagens de preview OpenGraph (fallback para GCS_BUCKET_NAME)Opcional
GCS_WORKSPACE_LOGOS_BUCKET_NAMELogos de workspace (fallback para GCS_BUCKET_NAME)Opcional

Um exemplo completo de Helm (Workload Identity, GKE) está em helm/sim/examples/values-gcp.yaml.

Configurar um provedor compatível com S3 (R2, MinIO, B2)

O Zoen funciona com qualquer store compatível com S3 apontando o client S3 para um endpoint customizado. Configure exatamente como AWS S3 (buckets, access key, secret) e depois adicione S3_ENDPOINT — e S3_FORCE_PATH_STYLE onde o provedor exigir addressing path-style. Verificado com Cloudflare R2, MinIO, Backblaze B2 e RustFS.

S3_ENDPOINT é configuração confiável do operador, então é usado como está — http:// e hosts privados são aceitos (sem gate de SSRF/HTTPS). Não conecte a input não confiável.

O endpoint precisa ser alcançável a partir dos navegadores dos usuários, e o bucket precisa de CORS. Uploads usam requisições PUT pré-assinadas enviadas diretamente do navegador para S3_ENDPOINT (downloads são proxyados de volta pelo app, então só precisam de alcance no lado do servidor). Isso significa:

  • Um endpoint puramente interno (ex.: https://minio.internal:9000 que só os pods do app resolvem) deixa o servidor subir normalmente, mas os uploads falham no navegador. Use um endpoint que seus usuários consigam alcançar.
  • Configure uma política CORS no bucket que permita a origem do seu Zoen (PUT, GET e os headers Authorization / Content-Type / x-amz-*). Isso vale para AWS S3 também — R2 e MinIO não são diferentes.

O Cloudflare R2 usa estilo virtual-hosted (o padrão) e a região auto:

AWS_REGION=auto
S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID=<r2-access-key-id>
AWS_SECRET_ACCESS_KEY=<r2-secret-access-key>
S3_BUCKET_NAME=myorg-sim-workspace-files
# ...variáveis S3_*_BUCKET_NAME restantes, um bucket R2 cada

Deixe S3_FORCE_PATH_STYLE sem definir — o R2 oferece suporte ao addressing virtual-hosted padrão.

O MinIO (e o Ceph RGW) precisam de addressing path-style e aceitam qualquer string de região:

AWS_REGION=us-east-1
S3_ENDPOINT=https://minio.example.com   # precisa ser alcançável pelos navegadores dos usuários, não só pelos pods do app
S3_FORCE_PATH_STYLE=true
AWS_ACCESS_KEY_ID=<minio-access-key>
AWS_SECRET_ACCESS_KEY=<minio-secret-key>
S3_BUCKET_NAME=myorg-sim-workspace-files
# ...variáveis S3_*_BUCKET_NAME restantes, um bucket cada

http:// funciona no lado do servidor, mas como o navegador faz upload direto para esse endpoint, prefira um endpoint TLS que seus usuários consigam alcançar (um alvo http:// de conteúdo misto será bloqueado em uma origem https:// do Zoen).

O RustFS é um store compatível com S3 baseado em Rust (drop-in do MinIO). Configure exatamente como o MinIO — path-style, qualquer string de região, access key/secret SigV4:

AWS_REGION=us-east-1
S3_ENDPOINT=https://rustfs.example.com   # precisa ser alcançável pelos navegadores dos usuários
S3_FORCE_PATH_STYLE=true
AWS_ACCESS_KEY_ID=<rustfs-access-key>
AWS_SECRET_ACCESS_KEY=<rustfs-secret-key>
S3_BUCKET_NAME=myorg-sim-workspace-files
# ...variáveis S3_*_BUCKET_NAME restantes, um bucket cada

Os mesmos requisitos de alcance pelo navegador e CORS se aplicam.

Verifique se funciona

Depois de reiniciar com a nova configuração:

  1. Abra o app e envie um documento para uma base de conhecimento (ou defina uma foto de perfil).
  2. Confirme que um objeto aparece no bucket/container correspondente.
  3. Recarregue a página — o arquivo ainda deve renderizar (downloads fazem stream de volta pelo app em /api/files/serve).

Se os uploads falharem, verifique os logs do app por erros de credencial ou permissão (veja Solução de problemas).

Common Questions

O Zoen cai para disco local, gravando arquivos no diretório /uploads dentro do container do app. Isso serve para avaliação, mas não é durável entre recriações de container e não é compartilhado entre réplicas — use S3, Azure Blob ou Google Cloud Storage em produção.
Não. Só AWS_REGION e S3_BUCKET_NAME são obrigatórios para ativar o modo S3. Os buckets por finalidade são recomendados para isolar cada tipo de arquivo; og-images e workspace-logos fazem fallback para o bucket geral se suas variáveis não estiverem definidas.
Em EC2/ECS/EKS, anexe a política IAM à role da instância, task role ou role IRSA da service account e deixe AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY sem definir. O Zoen resolve as credenciais automaticamente pela cadeia padrão do AWS SDK.
Não. O Zoen seleciona um único backend, na ordem Azure Blob, depois S3, depois Google Cloud Storage. Defina só as variáveis do backend que você quer.
Rode o Zoen com GKE Workload Identity (ou uma service account GCE anexada) e deixe GCS_CREDENTIALS_JSON sem definir — as credenciais resolvem via Application Default Credentials. Conceda à service account roles/iam.serviceAccountTokenCreator em si mesma para que signed URLs possam ser geradas pela API IAM signBlob.
Não, e não devem ser. Mantenha-os privados com acesso público bloqueado. O Zoen serve arquivos aos usuários por URLs pré-assinadas de curta duração, então os buckets nunca precisam de permissões de leitura pública.
Sim. Configure como AWS S3 e depois defina S3_ENDPOINT para o endpoint do seu provedor. Para R2, defina AWS_REGION=auto e deixe S3_FORCE_PATH_STYLE sem definir. Para MinIO/Ceph, defina S3_FORCE_PATH_STYLE=true. Veja a seção de provedor compatível com S3 acima.

On this page