O Studio tem três padrões de tráfego que quebram configurações de proxy padrão: websockets de longa duração, streams de server-sent events e uploads grandes. A maioria dos relatos de "funciona local, mas não em produção" vem de um desses três.
Topologia
Dois serviços precisam estar acessíveis. Você pode colocá-los em um hostname ou em dois.
O mais simples. Roteie /socket.io para o realtime e todo o resto para o app.
studio.yourdomain.com/ → app:3000
studio.yourdomain.com/socket.io → realtime:3002Você pode deixar NEXT_PUBLIC_SOCKET_URL sem valor — o cliente usa a origem da página por padrão.
Necessário em ingress controllers que não conseguem dividir caminhos entre backends de forma limpa, e preferível no load balancer nativo do GKE.
studio.yourdomain.com → app:3000
studio-ws.yourdomain.com → realtime:3002Depois informe ao cliente onde o realtime está e informe ao realtime quais origens aceitar:
app:
env:
NEXT_PUBLIC_APP_URL: "https://studio.yourdomain.com"
BETTER_AUTH_URL: "https://studio.yourdomain.com"
NEXT_PUBLIC_SOCKET_URL: "https://studio-ws.yourdomain.com"
realtime:
env:
ALLOWED_ORIGINS: "https://studio.yourdomain.com"ALLOWED_ORIGINS é a lista de origens permitidas (CORS) que o realtime aplica às conexões de socket; ela precisa conter a origem do app. As chaves de URL só precisam ser definidas uma vez em app.env — o chart as escreve em um Secret que os dois Deployments consomem.
Os dois hostnames precisam de registros DNS e certificados TLS.
Configuração do proxy reverso
O Caddy lida corretamente com certificados, websockets e streaming por padrão.
studio.yourdomain.com {
request_body {
max_size 250MB
}
handle /socket.io/* {
reverse_proxy localhost:3002
}
reverse_proxy localhost:3000 {
flush_interval -1
}
}flush_interval -1 desativa o buffer de resposta, o que mantém a saída do agente fluindo token por token em vez de chegar em um único bloco no final.
Por padrão, o Nginx faz buffer das respostas e encerra conexões inativas. É preciso sobrescrever os dois comportamentos.
server {
listen 443 ssl http2;
server_name studio.yourdomain.com;
# Large file uploads (chat attachments can reach ~220 MB)
client_max_body_size 250M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Streamed responses must not be buffered
proxy_buffering off;
proxy_cache off;
# Long-running workflow executions
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}No ingress-nginx, os equivalentes são annotations:
ingress:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "250m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"ingress:
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"Defina os timeouts de leitura e de inatividade no entrypoint, já que eles são configuração estática e não por ingress:
entryPoints:
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: 3600s
idleTimeout: 3600sO Traefik faz streaming das respostas por padrão e não precisa de ajuste de buffer.
Load balancers de nuvem
GKE (GCE ingress)
O load balancer do GCE usa um timeout de backend de 30 segundos por padrão, o que fecha todos os websockets a cada 30 segundos. Os clientes reconectam, então isso degrada em vez de quebrar — mas a colaboração parece instável e as tempestades de reconexão aumentam a carga. Corrija com um BackendConfig no Service do realtime.
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: studio-realtime-backendconfig
namespace: studio
spec:
timeoutSec: 3600
connectionDraining:
drainingTimeoutSec: 60Depois anote o Service do realtime para que o load balancer o reconheça:
realtime:
service:
annotations:
cloud.google.com/backend-config: '{"default": "studio-realtime-backendconfig"}'Confirme que a sua versão do chart renderiza realtime.service.annotations no Service (helm template ./helm/studio --values my-values.yaml | grep -A5 'kind: Service'). Se não renderizar, anote o Service diretamente com kubectl annotate.
O TLS no GKE normalmente usa um ManagedCertificate, que o chart referencia por annotation mas não cria — crie você mesmo antes do primeiro deploy:
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: studio-ssl-cert
namespace: studio
spec:
domains:
- studio.yourdomain.com
- studio-ws.yourdomain.comingress:
className: gce
annotations:
kubernetes.io/ingress.global-static-ip-name: "studio-ip"
networking.gke.io/managed-certificates: "studio-ssl-cert"
kubernetes.io/ingress.allow-http: "false"
# TLS comes from the ManagedCertificate — leaving the chart's secret-based
# TLS on makes the ingress reference a Secret that does not exist.
tls:
enabled: falseO certificado é provisionado assim que o DNS resolve, normalmente 15–30 minutos após o primeiro deploy.
AWS (ALB ingress)
ingress:
className: alb
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..."
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=3600O timeout de inatividade padrão de 60 segundos do ALB também fecha websockets. Aumente-o como mostrado acima.
Azure (Application Gateway / NGINX)
O timeout de requisição padrão do Application Gateway é de 30 segundos; aumente-o na configuração HTTP de backend. Muitas instalações no AKS usam o ingress-nginx em vez disso — veja a aba Nginx acima.
Limites de tamanho de requisição
O Studio aplica os próprios limites, além do que o seu proxy permitir. O limite do proxy precisa ser pelo menos tão grande quanto o limite do app, ou o proxy rejeita a requisição antes que o Studio a receba.
| Variável | Padrão | Aplica-se a |
|---|---|---|
API_MAX_JSON_BODY_BYTES | 50 MB | Rotas de API validadas por contrato |
CHAT_MAX_REQUEST_BYTES | 220 MB | O endpoint público de chat com deploy (cobre cerca de 15 arquivos anexados em base64) |
WEBHOOK_MAX_REQUEST_BYTES | 10 MB | Endpoints públicos que recebem webhooks |
Um limite de corpo de 250 MB no proxy acomoda os três padrões. Se você reduzir os limites do app, pode reduzir o limite do proxy na mesma medida.
Com object storage configurado, os uploads comuns de arquivo não passam pelo proxy — o navegador faz PUT direto no bucket usando uma URL pré-assinada, então os limites do proxy só valem para anexos de chat, payloads de API e corpos de webhook. No armazenamento local em disco (o padrão) não existe caminho pré-assinado e todo upload passa pelo proxy, então o limite de corpo dele se aplica a todos.
Conectividade de saída
O app faz chamadas de saída para provedores de modelos, APIs de integração, seu provedor de e-mail e o object storage. Não existe uma configuração global de forward proxy. O Studio não lê HTTP_PROXY / HTTPS_PROXY, então chamadas a provedores de modelos, chamadas de integração e entrega de e-mail não podem ser roteadas por um forward proxy. (O bloco HTTP Request aceita um proxyUrl por requisição, mas isso cobre apenas aquele bloco, não o tráfego de saída da própria plataforma.) Ambientes com proxy de egresso obrigatório precisam de um proxy transparente ou de egresso via NAT.