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 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 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
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.
| Setting | Symptom if missing |
|---|---|
proxy_set_header Host $http_host | Routing and link generation are misaligned |
Upgrade / Connection headers | Preview real-time refresh (HMR) WebSocket disconnects, so code changes are not reflected on screen |
proxy_read_timeout 3600s | Build 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 off | Streaming responses get stuck in the buffer, and replies appear in delayed chunks |
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 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 Item | Method |
|---|---|
| Studio loads | Access https://studio.company.com → log in |
| Preview loads | Open a project → preview screen displays, no Mixed Content in console |
| App path is open | Access https://studio.company.com/container/ → 404 or app list is normal (connected) |
| Real-time refresh | Edit a phrase via chat → preview refreshes automatically |
| Long builds | Run build chat for more than 1 minute → completes without "connection error" |
| Deployed app data | Open the deployed app → real data displays (no 401) |
Cautions
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.
- 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❌).
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
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.
| Symptom | Cause | Action |
|---|---|---|
Preview and deployed app are blank, Mixed Content in console | Studio is HTTPS, but app assets are requested over HTTP | Add X-Forwarded-Proto $scheme to the proxy, then use HTTPS throughout |
| Real data returns 401 in deployed app | Session logged in before the domain was added | Log out and log back in once |
| Build chat shows "connection error: network error" when it takes a long time | Proxy send timeout (default 60s) | proxy_read_timeout 3600s |
| Code changes are not reflected in preview | HMR WebSocket is not upgraded | Add Upgrade / Connection headers |
| Replies appear in delayed chunks | proxy_buffering is enabled (default) | proxy_buffering off |
| Any domain goes somewhere unexpected | web nginx conf.d load order (first server is the default server) | Start the vhost filename with zz- |
/container/… returns 404 | The TLS overlay copy lacks the /container/ block | Refer to the warning above |
| App doesn't appear in installations that use the port directly | Firewall blocks port 5171 | Open the firewall and verify with curl -I http://<host>:5171/ |
Related Documentation
- Installation — ports and firewall
- Secrets Management — how to apply environment variable changes
- Troubleshooting