도메인과 리버스 프록시
IP 주소 대신 도메인(특히 HTTPS)으로 스튜디오를 서비스할 때의 설정입니다. 이 장은 순서대로 다 해야 동작합니다. 하나라도 빠지면 "프리뷰가 빈 화면", "빌드 채팅이 연결 오류", "배포앱에서 데이터가 401" 같은 서로 달라 보이는 증상이 제각각 나타납니다.
도메인은 하나입니다
프리뷰와 배포앱은 스튜디오와 같은 도메인의 /container/ 아래에서 서빙됩니다.
https://studio.company.com/ → 스튜디오 UI + API
https://studio.company.com/container/… → 프리뷰 · 배포앱
필요한 것은 DNS 레코드 1개, 인증서 1장, vhost 1개입니다.
2026-08-23 이전에는 앱을 별도 호스트(studio-apps.company.com)로 분리하고 그 주소를
APPS_ORIGIN 이나 환경설정의 "앱 오리진"으로 알려 줘야 했습니다. 그 설정은 없어졌습니다.
없앤 이유는 그 값 하나가 비면 서버가 프리뷰 URL 을 요청호스트:5171 로 만들어 내는데,
80/443 만 공개하는 리버스 프록시 구성에서는 브라우저가 거기에 붙지 못해 라이브 프리뷰가
통째로 빈 화면이 됐기 때문입니다. 상대 경로만 쓰면 애초에 틀릴 수가 없습니다.
.env 나 환경설정에 옛 APPS_ORIGIN 값이 남아 있어도 지금은 읽지 않습니다.
프리뷰·배포앱은 채팅으로 생성된 코드, 즉 신뢰할 수 없는 콘텐츠입니다. 예전 구조는 오리진을 분리해 그 자바스크립트가 스튜디오 로그인 토큰에 접근하지 못하게 막았습니다. 지금은 같은 오리진에서 돌아 그 격리가 없습니다. 도메인을 하나로 줄이는 대가로 감수한 판단입니다.
따라서 신뢰할 수 없는 사용자에게 앱 생성 권한을 주지 마세요. 빌더 역할은 사내 운영자에게만 부여하는 것을 전제로 한 구성입니다.
준비물 체크리스트
- DNS A 레코드 1개
- 해당 호스트네임의 TLS 인증서
- 리버스 프록시에서 스튜디오 박스의 :80 에 도달 가능
- 스튜디오 박스 방화벽에서 프록시 → :80 허용
앱 리스너(:5171)는 스튜디오 박스 안의 web nginx 가 /container/ · /apps/ · /preview/
를 넘겨주므로 바깥에 노출할 필요가 없습니다. 리버스 프록시 없이 포트로 직접 접속하는
설치에서만 방화벽을 열어 주세요.
1. 프록시에 vhost 추가
바깥 프록시는 전부 스튜디오 박스의 :80 으로 넘기기만 하면 됩니다. /container/ ·
/apps/ · /preview/ 분기는 박스 안의 web nginx 가 이미 처리합니다.
반드시 들어가야 하는 4가지
빠뜨렸을 때 나타나는 증상이 서로 달라 원인을 찾기 어려우므로, 아래 예시를 그대로 복사해 쓰는 것을 권장합니다.
| 설정 | 빠지면 생기는 증상 |
|---|---|
proxy_set_header Host $http_host | 라우팅·링크 생성이 어긋납니다 |
Upgrade / Connection 헤더 | 프리뷰 실시간 갱신(HMR) 웹소켓이 끊겨 코드 수정이 화면에 반영되지 않습니다 |
proxy_read_timeout 3600s | 빌드 채팅이 "연결 오류: network error" — 에이전트가 도구 실행으로 수십 초 침묵하면 기본 60초 타임아웃이 스트림을 끊습니다 |
proxy_buffering off | 스트리밍 응답이 버퍼에 고여 답변이 뭉텅이로 늦게 뜹니다 |
빌드 채팅과 질의 응답은 SSE 로 실시간 전송됩니다. 서버가 25초마다 하트비트를 보내므로
프록시 타임아웃이 그보다 길기만 하면 되지만, 3600s 는 실제 운영에서 검증된 값입니다.
nginx 설정 예시
server {
listen 80;
server_name studio.company.com;
location / {
proxy_pass http://<스튜디오박스>: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 는 certbot 등으로 발급합니다 — listen 443 ssl 블록이 위 설정을 그대로 상속하면
됩니다.
패키징된 web nginx 컨테이너의 conf.d 에 직접 vhost 파일을 마운트한다면, 파일명을
zz- 로 시작하게 하세요. nginx 는 알파벳순으로 첫 번째 server 블록을 기본 서버로
삼기 때문에, 이름이 앞서면 매칭되지 않는 모든 요청이 엉뚱한 곳으로 빠져 스튜디오
전체가 죽습니다(실제 발생한 사고입니다).
2. 재로그인 (필수)
도메인 설정을 마쳤으면 모든 사용자가 한 번 로그아웃 후 다시 로그인해야 합니다.
로그인 쿠키는 발급 시점의 호스트 전용입니다. IP 로 접속해 로그인해 둔 세션은 도메인으로 바꾼 뒤 전달되지 않아 데이터 요청이 401 로 실패합니다.
3. 확인
| 확인 항목 | 방법 |
|---|---|
| 스튜디오가 뜬다 | https://studio.company.com 접속 → 로그인 |
| 프리뷰가 뜬다 | 프로젝트 열기 → 프리뷰 화면 표시, 콘솔에 Mixed Content 없음 |
| 앱 경로가 뚫려 있다 | https://studio.company.com/container/ 접속 → 404/앱 목록이면 정상(연결됨) |
| 실시간 갱신 | 채팅으로 문구 하나 수정 → 프리뷰가 자동 갱신 |
| 긴 빌드 | 빌드 채팅 1분 이상 실행 → "연결 오류" 없이 완주 |
| 배포앱 데이터 | 배포된 앱 열기 → 실데이터 표시(401 없음) |
주의사항
도메인을 붙인 뒤에는 도메인으로만 접속하세요. IP 로 접속한 사용자는 쿠키가 달라 배포앱 데이터가 401 이 됩니다.
- WebSocket 은 기본 지원되므로 별도 설정이 필요 없습니다.
- 무료 유니버설 인증서는 1단 서브도메인만 커버합니다(
studio.company.com✅,apps.studio.company.com❌).
현장 사진·도면을 채팅에 첨부하므로 client_max_body_size 64M 를 넣으세요.
빠지면 큰 사진 업로드가 413 으로 실패합니다.
리버스 프록시 없이 HTTPS 만 붙이기
별도 프록시 서버가 없고 스튜디오 박스에서 바로 TLS 를 종단하려면 운영 패키지의 TLS 오버레이를 씁니다.
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 에는 /container/ 블록이 없고, 주석이 아직 없어진
APPS_ORIGIN 과 별도 포트(5443)를 안내합니다. 그대로 쓰면 프리뷰·배포앱 주소
(/container/…)가 404 가 됩니다.
당분간은 사본(tls/nginx-tls.conf)에 /container/ 블록을 직접 추가하세요 — 위쪽
nginx 설정 예시의 헤더 4종을 그대로 쓰고, 접두사를 떼어 앱
리스너로 넘기면 됩니다. APPS_ORIGIN 줄은 넣지 마세요(읽지 않습니다).
증상 → 원인 빠른 대조
도메인 배포에서 실제로 자주 겪는 것들입니다. 더 넓은 범위는 트러블슈팅에 있습니다.
| 증상 | 원인 | 조치 |
|---|---|---|
프리뷰·배포앱이 빈 화면, 콘솔에 Mixed Content | 스튜디오는 HTTPS 인데 앱 자산이 HTTP 로 요청됨 | 프록시에 X-Forwarded-Proto $scheme 추가 후 전체를 HTTPS 로 |
| 배포앱에서 실데이터가 401 | 도메인 붙이기 전에 로그인한 세션 | 로그아웃 후 재로그인 1회 |
| 빌드 채팅이 오래 걸리면 "연결 오류: network error" | 프록시 무전송 타임아웃(기본 60초) | proxy_read_timeout 3600s |
| 코드 수정이 프리뷰에 반영 안 됨 | HMR 웹소켓이 업그레이드되지 않음 | Upgrade / Connection 헤더 추가 |
| 답변이 뭉텅이로 늦게 뜸 | proxy_buffering 이 켜져 있음(기본값) | proxy_buffering off |
| 아무 도메인이나 엉뚱한 곳으로 빠짐 | web nginx conf.d 로드 순서(첫 server 가 기본 서버) | vhost 파일명을 zz- 로 시작 |
/container/… 가 404 | TLS 오버레이 사본에 /container/ 블록이 없음 | 위 경고 참조 |
| 포트로 직접 쓰는 설치에서 앱이 안 뜸 | 방화벽에서 5171 차단 | 방화벽 개방 후 curl -I http://<박스>:5171/ 로 확인 |