Rede

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:3002

Você 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:3002

Depois 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: 3600s

O 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: 60

Depois 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.com
ingress:
  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: false

O 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=3600

O 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ávelPadrãoAplica-se a
API_MAX_JSON_BODY_BYTES50 MBRotas de API validadas por contrato
CHAT_MAX_REQUEST_BYTES220 MBO endpoint público de chat com deploy (cobre cerca de 15 arquivos anexados em base64)
WEBHOOK_MAX_REQUEST_BYTES10 MBEndpoints 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.

Common Questions

Não. Um único hostname funciona se o seu proxy conseguir rotear /socket.io para o serviço de realtime e todo o resto para o app. Dois hostnames são úteis quando a divisão por caminho é complicada, o que é comum no load balancer nativo do GKE.
O load balancer do GCE usa um timeout de backend de 30 segundos por padrão. Crie um BackendConfig com timeoutSec: 3600 e associe-o ao Service do realtime com a annotation cloud.google.com/backend-config. Na AWS, aumente o idle_timeout do ALB em vez disso.
Não de forma global. O Studio não lê HTTP_PROXY nem HTTPS_PROXY, então o tráfego da plataforma não pode ser roteado por um forward proxy. O bloco HTTP Request aceita um proxyUrl por requisição, válido apenas para as próprias chamadas dele. Use um proxy transparente ou egresso via NAT se a sua rede exigir.