Skip to main content

Domains and Reverse Proxy

This section describes the setup for serving Studio via a domain (particularly HTTPS) instead of an IP address. Every step in this chapter must be completed in order for it to work. If any step is missed, symptoms that seem unrelated—such as "blank preview screen," "build chat connection error," or "data returns 401 in the deployed app"—will appear individually.


There is only one domain​

The preview and deployed app are served under a /container/ that is the same domain as Studio.

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

What you need is one DNS record, one certificate, and one vhost.

The app also uses the same domain

The app is not split onto a separate host. Because all preview and deployment addresses are relative paths, this works as intended in a reverse proxy setup that only exposes ports 80/443.

The trade-off — origin isolation is given up

The preview and deployed app contain code generated by chat, which is untrusted content. It runs on the same origin as Studio — which means there is no isolation that would come from separate origins. This is a decision made to accept this trade-off in exchange for reducing to a single domain.

Therefore, do not grant app creation permissions to untrusted users. This configuration assumes that the builder role is assigned only to in-house operators.


Prerequisites Checklist​

  • One DNS A record
  • TLS certificate for that hostname
  • Reverse proxy can reach Studio box's :80
  • Studio box firewall allows proxy → :80
:5171 does not need to be opened

The app listener (:5171) is forwarded by the web nginx inside the Studio box for /container/ · /apps/ · /preview/, so it does not need to be exposed to the outside. Only open the firewall in installations that connect directly to the port without a reverse proxy.


1. Add a vhost to the proxy​

The outer proxy only needs to forward everything to the Studio box's :80. /container/ The /apps/ · /preview/ branches are already handled by the web nginx inside the box.

The 4 things that must be included​

Because the symptoms from missing any of these differ, making it difficult to find the cause, it is recommended to copy the example below and use it as-is.

SettingSymptom if missing
proxy_set_header Host $http_hostRouting and link generation are misaligned
Upgrade / Connection headersPreview real-time refresh (HMR) WebSocket disconnects, so code changes are not reflected on screen
proxy_read_timeout 3600sBuild chat shows "connection error: network error" — if the agent is silent for tens of seconds while executing tools, the default 60-second timeout interrupts the stream
proxy_buffering offStreaming responses get stuck in the buffer, and replies appear in delayed chunks
SSE (Server-Sent Events streaming) is critical

Build chat and Q&A responses are transmitted in real time via SSE. Since the server sends a heartbeat every 25 seconds, it's sufficient for the proxy timeout to be longer than that, but 3600s is a value verified in real operation.

nginx configuration 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; # 사진·도면 첨부
}
}

HTTPS is issued via certbot or similar — the listen 443 ssl block just needs to inherit the above settings.

If you are adding a vhost to the Studio box's web nginx

If you mount a vhost file directly into the conf.d of the packaged web nginx container, make sure the filename starts with zz-. Because nginx treats the first server block in alphabetical order as the default server, if the name comes first alphabetically, all non-matching requests will be routed somewhere unexpected, breaking the entire Studio (this has actually happened).


2. Re-login (Required)​

Once the domain setup is complete, all users must log out and then log back in once.

Login cookies are host-specific at the time of issuance. Sessions logged in via IP will not be carried over after switching to the domain, so data requests will fail with 401.


3. Verification​

Check ItemMethod
Studio loadsAccess https://studio.company.com → log in
Preview loadsOpen a project → preview screen displays, no Mixed Content in console
App path is openAccess https://studio.company.com/container/ → 404 or app list is normal (connected)
Real-time refreshEdit a phrase via chat → preview refreshes automatically
Long buildsRun build chat for more than 1 minute → completes without "connection error"
Deployed app dataOpen the deployed app → real data displays (no 401)

Cautions​

Don't mix IP and domain

After adding a domain, access only via the domain. Users who access via IP will have different cookies, causing deployed app data to return 401.

When putting Cloudflare in front
  • WebSocket is supported by default, so no special configuration is required.
  • The free universal certificate covers only 1-level subdomains (studio.company.com ✅, apps.studio.company.com ❌).
Attachment size

Because field photos and drawings are attached to chat, set client_max_body_size 64M. If it's missing, large photo uploads will fail with 413.


Adding HTTPS only Without a Reverse Proxy​

If you do not have a separate proxy server and want to 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 does not have a /container/ block, and the comments still describe an outdated APPS_ORIGIN and a separate port (5443). If you use it as-is, the preview and deployed app addresses (/container/…) will return 404.

For now, add a /container/ block directly to a copy (tls/nginx-tls.conf) — use the same 4 headers from the nginx configuration example above, and remove the prefix to forward to the app listener. Do not add the APPS_ORIGIN line (it will not be read).


Quick symptom → cause reference​

These are things commonly encountered in real domain deployments. A broader scope is covered in Troubleshooting.

SymptomCauseAction
Preview and deployed app are blank, Mixed Content in consoleStudio is HTTPS, but app assets are requested over HTTPAdd X-Forwarded-Proto $scheme to the proxy, then use HTTPS throughout
Real data returns 401 in deployed appSession logged in before the domain was addedLog out and log back in once
Build chat shows "connection error: network error" when it takes a long timeProxy send timeout (default 60s)proxy_read_timeout 3600s
Code changes are not reflected in previewHMR WebSocket is not upgradedAdd Upgrade / Connection headers
Replies appear in delayed chunksproxy_buffering is enabled (default)proxy_buffering off
Any domain goes somewhere unexpectedweb nginx conf.d load order (first server is the default server)Start the vhost filename with zz-
/container/… returns 404The TLS overlay copy lacks the /container/ blockRefer to the warning above
App doesn't appear in installations that use the port directlyFirewall blocks port 5171Open the firewall and verify with curl -I http://<host>:5171/