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:
| Backend | Quando usar |
|---|---|
| Disco local | Docker single-node, desenvolvimento local, avaliação |
| AWS S3 | Produção, especialmente com mais de uma réplica do app |
| Azure Blob | Produção no Azure |
| Google Cloud Storage | Produçã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:
- Azure Blob — usado se
AZURE_STORAGE_CONTAINER_NAMEestiver definido e (AZURE_ACCOUNT_NAME+AZURE_ACCOUNT_KEY) ouAZURE_CONNECTION_STRINGestiver definido. - AWS S3 — usado se
S3_BUCKET_NAMEeAWS_REGIONestiverem definidos (e o Azure não estiver configurado). - Google Cloud Storage — usado se
GCS_BUCKET_NAMEestiver definido (e nem Azure nem S3 estiverem configurados). - 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"
doneEm 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_KEYsem 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-logosSó 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ável | Armazena | Obrigatório |
|---|---|---|
AWS_REGION | Região de todos os buckets | Sim (ativa o S3) |
AWS_ACCESS_KEY_ID | Access key | Não (usa a cadeia de credenciais se não definido) |
AWS_SECRET_ACCESS_KEY | Secret key | Não (usa a cadeia de credenciais se não definido) |
S3_BUCKET_NAME | Arquivos gerais do workspace | Sim (ativa o S3) |
S3_KB_BUCKET_NAME | Documentos de bases de conhecimento | Recomendado |
S3_EXECUTION_FILES_BUCKET_NAME | Arquivos de execução de workflows (padrão: sim-execution-files) | Recomendado |
S3_CHAT_BUCKET_NAME | Assets de chats publicados | Recomendado |
S3_COPILOT_BUCKET_NAME | Anexos do Copilot | Recomendado |
S3_PROFILE_PICTURES_BUCKET_NAME | Avatares de usuário | Recomendado |
S3_OG_IMAGES_BUCKET_NAME | Imagens de preview OpenGraph (fallback para S3_BUCKET_NAME) | Opcional |
S3_WORKSPACE_LOGOS_BUCKET_NAME | Logos de workspace (fallback para S3_BUCKET_NAME) | Opcional |
S3_LOGS_BUCKET_NAME | Logs armazenados | Opcional |
S3_ENDPOINT | Endpoint customizado para storage compatível com S3 (R2, MinIO, B2) | Opcional (AWS S3 se não definido) |
S3_FORCE_PATH_STYLE | true 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 -dComo 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 restantesNo 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-logosUm 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
doneMantenha 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
doneOs 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
doneVocê 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_JSONsem definir. Como não há chave privada nesse modo, a geração de signed URLs usa a API IAMsignBlob— conceda à service accountroles/iam.serviceAccountTokenCreatorem 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_JSONcom 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-logosSó 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ável | Armazena | Obrigatório |
|---|---|---|
GCS_BUCKET_NAME | Arquivos gerais do workspace | Sim (ativa o GCS) |
GCS_PROJECT_ID | ID do projeto GCP | Não (inferido das credenciais/ADC) |
GCS_CREDENTIALS_JSON | JSON inline da service account | Não (usa Application Default Credentials se não definido) |
GCS_KB_BUCKET_NAME | Documentos de bases de conhecimento | Recomendado (fallback para GCS_BUCKET_NAME) |
GCS_EXECUTION_FILES_BUCKET_NAME | Arquivos de execução de workflows | Recomendado (fallback para GCS_BUCKET_NAME) |
GCS_CHAT_BUCKET_NAME | Assets de chats publicados | Recomendado (fallback para GCS_BUCKET_NAME) |
GCS_COPILOT_BUCKET_NAME | Anexos do Copilot | Recomendado (fallback para GCS_BUCKET_NAME) |
GCS_PROFILE_PICTURES_BUCKET_NAME | Avatares de usuário | Recomendado (fallback para GCS_BUCKET_NAME) |
GCS_OG_IMAGES_BUCKET_NAME | Imagens de preview OpenGraph (fallback para GCS_BUCKET_NAME) | Opcional |
GCS_WORKSPACE_LOGOS_BUCKET_NAME | Logos 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:9000que 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,GETe os headersAuthorization/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 cadaDeixe 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 cadahttp:// 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 cadaOs mesmos requisitos de alcance pelo navegador e CORS se aplicam.
Verifique se funciona
Depois de reiniciar com a nova configuração:
- Abra o app e envie um documento para uma base de conhecimento (ou defina uma foto de perfil).
- Confirme que um objeto aparece no bucket/container correspondente.
- 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).