Skip to main content

Domain and Reverse Proxy

Configuration for serving Studio over a domain (especially HTTPS) instead of an IP address. This section must be completed in order. If any step is skipped, you will see seemingly unrelated symptoms: "preview shows blank screen," "build chat connection error," "deployed app returns 401," and so on.


One domain for everything

Preview and deployed app are served under the same domain as Studio, at /container/.

https://studio.company.com/ → 스튜디오 UI + API
https://studio.company.com/container/… → 프리뷰 · 배포앱

All you need is one DNS record, one certificate, one vhost.

Previously there were two domains

Before 2026-08-23, the app was separated to a different host (studio-apps.company.com) and you had to tell Studio its address via APPS_ORIGIN or the "App Origin" setting. That setting no longer exists.

We removed it because if that one value was empty, the server would generate preview URLs as 요청호스트:5171, and in a reverse-proxy setup that only exposes ports 80/443, the browser could not reach them — live preview would be completely blank. Using only relative paths means there is no way to get it wrong.

Even if an old APPS_ORIGIN value remains in .env or environment settings, it is now not read.

The trade-off — we gave up origin isolation

Preview and deployed app contain code generated by chat, so untrusted content. The old structure isolated origins so that JavaScript could not access Studio login tokens. Now they run on the same origin, so that isolation is gone. We made this trade-off to reduce the domain to one.

Therefore do not grant app creation permission to untrusted users. This architecture assumes the builder role is given only to internal operators.


Checklist

  • DNS A record: one only
  • TLS certificate for that hostname
  • Reverse proxy can reach Studio box :80
  • Studio box firewall allows proxy → :80
You do not need to open :5171

The app listener (:5171) is served by web nginx inside the Studio box, which forwards /container/ · /apps/ · /preview/, so there is no need to expose it outside. Only open the firewall port in installations that access directly by port without a reverse proxy.


1. Add vhost to proxy

The external proxy should forward everything to Studio box :80. Branching for /container/ · /apps/ · /preview/ is already handled by web nginx inside the box.

Four essential settings

Symptoms differ when any is missing, making root cause hard to find. We recommend copying the example below exactly as-is.

SettingSymptom if missing
proxy_set_header Host $http_hostRouting and link generation become misaligned
Upgrade / Connection headerLive reload (HMR) WebSocket breaks; code changes don't appear in preview
proxy_read_timeout 3600sBuild chat shows "connection error: network error" — when the agent is silent for dozens of seconds during tool execution, the default 60-second timeout closes the stream
proxy_buffering offStreaming response buffers; answers arrive in chunks with delay
Server-sent events (SSE) are critical

Build chat and Q&A stream in real time via SSE. The server sends heartbeat every 25 seconds, so proxy timeout just needs to be longer, but 3600s is the value validated in production.

nginx config example

server {
listen 80;
server_name studio.company.com;

location / {
proxy_pass http://<studio-host>:80;
proxy_http_version 1.1;
proxy_set_header Host $http_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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s; # 에이전트 SSE(빌드 채팅) — 필수
proxy_buffering off; # 스트리밍 즉시 전달 — 필수
client_max_body_size 64M; # 사진·도면 첨부
}
}

For HTTPS, issue a certificate with certbot or similar — the listen 443 ssl block will inherit the config above.

If adding a vhost inside the Studio box web nginx

If you mount vhost files directly into conf.d of the packaged web nginx container, make the filename start with zz-. nginx picks the first server block alphabetically as the default server, so if your name comes first, all unmatched requests fall through elsewhere, and the entire Studio dies (this has actually happened).


2. Re-login (mandatory)

After configuring the domain, all users must log out and log back in once.

Login cookies are host-specific at the time of issue. Sessions logged in via IP will not carry over when you switch to the domain, and data requests will fail with 401.


3. Verification

ItemHow to check
Studio loadsAccess https://studio.company.com → log in
Preview loadsOpen project → preview displays, no Mixed Content in console
App path is openAccess https://studio.company.com/container/ → 404 or app list is normal (connected)
Live reloadEdit text in chat → preview auto-refreshes
Long buildRun build chat for 1+ minute → completes without "connection error"
Deployed app dataOpen deployed app → shows live data (no 401)

Cautions

Do not mix IP and domain access

After adding the domain, access only by domain. Users who access by IP will have different cookies, and deployed app data will return 401.

When using Cloudflare in front
  • WebSocket is supported by default, no additional config needed.
  • The free universal certificate covers only single-level subdomains (studio.company.com ✅, apps.studio.company.com ❌).
Attachment file size

Field photos and drawings are attached to chat, so add client_max_body_size 64M. Without it, uploading large photos will fail with 413.


HTTPS-only without reverse proxy

If you have no separate proxy server and terminate TLS directly on the Studio box, use the TLS overlay from the operations package.

cd /opt/kopens/plantpulse-studio-docker
cp tls/nginx-tls.conf.example tls/nginx-tls.conf # server_name 등 수정
# tls/cert.pem, tls/key.pem 배치(사내 CA 또는 공인 인증서)
docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d
The TLS overlay example file still contains the old structure

tls/nginx-tls.conf.example lacks /container/ blocks, and comments still mention the (now-removed) APPS_ORIGIN and a separate port (5443). Using it as-is will cause preview and deployed app addresses (/container/…) to return 404.

For now, make a copy (tls/nginx-tls.conf) and add /container/ blocks manually — use the same four headers from the nginx config example above, and forward without the prefix to the app listener. Do not add APPS_ORIGIN lines (they are not read).


Symptom → cause quick reference

These are common real-world issues with domain deployment. See Troubleshooting for broader coverage.

SymptomCauseAction
Preview and app are blank; console shows Mixed ContentStudio is HTTPS but app assets request HTTPAdd X-Forwarded-Proto $scheme to proxy, then make everything HTTPS
Deployed app data returns 401Session logged in before adding domainLog out and re-login once
Build chat times out with "connection error: network error"Proxy idle timeout (default 60s)proxy_read_timeout 3600s
Code edits don't appear in previewHMR WebSocket not upgradedAdd Upgrade / Connection headers
Answers arrive in chunks with delayproxy_buffering is on (default)proxy_buffering off
Random domain falls through to wrong placeweb nginx conf.d load order (first server is default server)Start vhost filename with zz-
/container/… returns 404TLS overlay copy missing /container/ blockSee warning above
App won't load in port-direct installationFirewall blocks 5171Open firewall, then check with curl -I http://<host>:5171/