콘텐츠로 이동

로컬 머신에 셀프 호스팅

이 가이드는 단일 호스트 셀프 호스팅 Chorus를 구성합니다. 애플리케이션 컨테이너 하나와 PostgreSQL 17 컨테이너 하나를 Chorus 저장소의 deploy/self-host/ 디렉터리에 있는 Docker Compose 파일로 시작합니다. install.sh의 로컬 설정(Chorus 0.22.2 이상)이 이미지를 준비하고, 보호된 설정을 작성하며, 이후 재배포에 쓸 래퍼 스크립트를 저장합니다. LAN용 자체 서명 HTTPS 엔드포인트를 추가하거나, 직접 만든 Cloudflare Named Tunnel로 공개할 수도 있습니다.

전체 원본 참고 문서는 저장소의 docs/SELF_HOST.md(중국어: docs/SELF_HOST.zh.md)입니다. 이 페이지는 대부분의 설치에 필요한 단계로 요약한 것입니다.

원하는 것사용할 방법
Docker 없이 혼자 Chorus를 써 보기내장 PGlite를 쓰는 npm 패키지: 시작하기
내 Linux 또는 macOS 호스트에서 상시 운영하고, LAN에서 접근하며, 실제 PostgreSQL을 사용이 가이드
게시된 이미지로 나만의 Compose 파일 작성Docker로 배포
다중 인스턴스, 관리형 PostgreSQL과 Redis, 또는 AWS프로덕션 배포

이 구성은 단일 인스턴스용입니다. Redis는 의도적으로 비활성화되어 있어(REDIS_URL이 비어 있음) 실시간 업데이트는 메모리 내 전달을 사용하며, 앱 컨테이너가 하나일 때만 동작합니다.

  • Docker Engine이 설치된 Linux 호스트, 또는 Linux 컨테이너를 실행하는 macOS + Docker Desktop. up --wait을 지원하는 Docker Compose v2. docker version과 docker compose version으로 확인합니다.
  • Bash 3.2 이상, OpenSSL(또는 LibreSSL), curl. 선택 사항인 스크립트 로그인 확인에는 jq가 필요합니다. 서버에는 Node.js, pnpm, AWS CLI가 필요하지 않습니다.
  • deploy/self-host/가 포함된 Chorus 저장소 체크아웃.
  • macOS에서는 먼저 Docker Desktop을 시작하고, 체크아웃과 비공개 설정·백업 경로를 공유하도록 허용합니다.
  • 이미지 레지스트리에 대한 네트워크 접근(Tunnel 모드에서는 Cloudflare 접근도 필요).

체크아웃 루트에서 설치 프로그램을 실행하고 로컬 대상을 선택합니다.

Terminal window
bash install.sh --target local

--target 없이 bash install.sh를 실행하면 먼저 aws와 local 중 하나를 묻습니다. 기본값은 여전히 AWS입니다. 터미널에서 로컬 설정은 이어서 다음을 묻습니다.

  1. 이미지 소스: pull(기본값)은 기존 릴리스 이미지를 내려받고, build는 현재 체크아웃을 해당 Dockerfile(production 타깃)로 빌드합니다.
  2. pull이면 이미지 태그 또는 다이제스트. 예: chorusaidlc/chorus-app:vX.Y.Z. 암시적 태그나 latest는 거부됩니다. build 모드는 기본적으로 이미지에 chorus-local:<project>(즉 chorus-local:chorus-local) 태그를 붙입니다.
  3. 일반 로그인 이메일과 비밀번호, 그리고 별도의 슈퍼 관리자 이메일과 비밀번호. 비밀번호 입력은 숨겨지며 두 번 입력합니다.
  4. Also enable self-signed LAN HTTPS? [y/N]. 기본값은 no입니다. 아래 “선택: 자체 서명 LAN HTTPS”를 참고하세요.

두 비밀번호 모두 한 줄이며 8바이트 이상이어야 하고, bcrypt 제약 때문에 슈퍼 관리자 비밀번호는 최대 72바이트입니다. bcrypt 해시를 직접 만들 필요는 없습니다. 설치 프로그램이 헬퍼 이미지 httpd:2.4-alpine을 받아, 네트워크가 없는 일회용 컨테이너에서 htpasswd(cost 12)로 해시합니다. 비밀번호는 인수가 아니라 stdin으로 전달됩니다.

스크립트로 설치할 때는 각 비밀번호를 체크아웃 밖의 별도 파일에 한 줄씩 저장합니다(디렉터리 0700, 파일 0600). 설치 프로그램은 심볼릭 링크와 체크아웃 내부 파일을 거부합니다. 다음 두 형식 중 하나를 선택하세요.

Terminal window
# 기존 릴리스 이미지 받기
bash install.sh --target local --image-source pull \
--image chorusaidlc/chorus-app:vX.Y.Z --project chorus-local \
--email owner@example.com --admin-email admin@example.com \
--password-file /absolute/private/path/login-password \
--admin-password-file /absolute/private/path/admin-password \
--port 18637 --non-interactive
Terminal window
# 현재 체크아웃을 chorus-local:chorus-local로 빌드
bash install.sh --target local --image-source build \
--project chorus-local --email owner@example.com --admin-email admin@example.com \
--password-file /absolute/private/path/login-password \
--admin-password-file /absolute/private/path/admin-password \
--port 18637 --non-interactive

--dry-run을 추가하고 두 비밀번호 파일 옵션을 빼면, 해시 계산·Docker 호출·파일 쓰기 없이 계획만 출력합니다. 터미널이 아닌 입력에서는 설치 프로그램이 답을 기다리지 않으며, 필수 값이 없으면 실패합니다. 옵션 요약은 bash install.sh --help로 볼 수 있습니다.

옵션대응 환경 변수기본값
--target localCHORUS_INSTALL_TARGET=localaws
--image-source pull|buildCHORUS_IMAGE_SOURCEpull
--imageCHORUS_IMAGEpull에서는 필수, build에서는 chorus-local:<project>
--projectCHORUS_LOCAL_PROJECTchorus-local
--portCHORUS_PORT18637
--email, --admin-emailDEFAULT_USER, SUPER_ADMIN_EMAIL없음
--password-file, --admin-password-fileDEFAULT_PASSWORD, SUPER_ADMIN_PASSWORD없음
--non-interactive, --dry-runCHORUS_INSTALL_NONINTERACTIVE=1, CHORUS_INSTALL_DRY_RUN=1꺼짐

명령줄 옵션이 환경 변수보다 우선하고, 비밀번호 파일이 비밀번호 변수보다 우선합니다. 명령줄에서 평문 비밀번호를 받는 옵션은 없습니다.

경로내용
~/.config/chorus-self-host/<project>/새 비공개 디렉터리, 모드 0700.
…/<project>/env모드 0600. 이미지 참조(CHORUS_IMAGE, POSTGRES_IMAGE=postgres:17-alpine), 무작위 POSTGRES_PASSWORD, DEFAULT_USER와 평문 일반 DEFAULT_PASSWORD, CHORUS_PORT, SUPER_ADMIN_EMAIL, 그리고 bcrypt 해시만 담은 SUPER_ADMIN_PASSWORD_HASH. 비밀로 취급하세요.
…/<project>/lan-tls/, …/<project>/lan-https.ymlLAN HTTPS를 선택한 경우에만.
체크아웃의 default_deploy_local.sh모드 0700, 비밀이 없는 재배포 래퍼. CHORUS_DEPLOY_CONFIG로 다른 경로를 지정할 수 있으며, 부모 디렉터리가 이미 있어야 합니다.

설치는 새 설치 전용입니다. 해당 프로젝트의 비공개 디렉터리, 래퍼 파일, 또는 Docker 컨테이너·볼륨·네트워크가 이미 있으면 설치 프로그램은 거부하고 아무것도 바꾸지 않습니다. 비밀번호를 교체하거나 데이터베이스를 재사용하지 않습니다. 시작에 실패하면 새 프로젝트의 컨테이너와 설정은 조사를 위해 남겨 둡니다.

NEXTAUTH_SECRET은 비워 두므로, 앱은 app-data 볼륨의 /app/data/.secret에 서명 키를 생성합니다. 이 볼륨을 유지하세요. 잃으면 모든 사용자가 로그아웃됩니다.

설치가 끝나면 URL, 비공개 env 파일 위치, 재배포 명령, 그리고 해당 프로젝트로 한정된 docker compose … stop 명령이 출력됩니다.

  • 호스트에서: http://localhost:18637(또는 지정한 --port).
  • 다른 LAN 기기에서: http://<호스트 LAN IP>:18637. 0.0.0.0이 아니라 호스트의 실제 주소를 사용하세요.
  • 설정한 일반 로그인으로 로그인합니다. /admin 패널은 별도의 슈퍼 관리자 계정을 사용합니다.

내장 이메일/비밀번호 로그인은 부트스트랩과 데모용이며 팀 SSO가 아닙니다. 팀에서 쓰려면 관리 패널에서 회사별로 OIDC를 설정하세요. 셀프 호스팅 Chorus 운영하기를 참고하세요. LAN 평문 HTTP는 자격 증명이나 세션을 암호화하지 않으며, 브라우저는 안전하지 않은 HTTP 오리진에서 OIDC 클라이언트가 PKCE에 필요로 하는 Web Crypto API를 제공하지 않으므로 OIDC에는 HTTPS가 필요합니다.

저장된 래퍼는 로컬 모드에서 필요한 오버라이드를 모두 불러올 때만 설치 프로그램 대신 사용하세요. 터널 모드나 oidc-only.yml 등 사용자 설정이 있다면 원하는 앱 이미지를 준비하고 완전한 Compose 호출을 사용해야 합니다. 래퍼는 해당 설정을 누락합니다.

Terminal window
bash ./default_deploy_local.sh --dry-run
bash ./default_deploy_local.sh

래퍼는 --dry-run만 받으며, 저장된 프로젝트·비공개 디렉터리·이미지 소스로 deploy/self-host/deploy-local.sh를 호출합니다. 실행할 때마다 다음을 합니다.

  • 현재 체크아웃을 다시 빌드(build 모드)하거나 저장된 CHORUS_IMAGE를 받습니다(pull 모드). pull 모드 인스턴스를 새 릴리스로 올리려면 먼저 비공개 env의 CHORUS_IMAGE를 수정하세요.
  • 저장된 POSTGRES_IMAGE는 로컬에 없을 때만 받습니다. 이것은 절대 데이터베이스 메이저 버전 업그레이드가 아닙니다.
  • 비공개 디렉터리에 https.yml, pg17.yml, lan-https.yml이 있으면 그 오버라이드를 추가하고, 해당 애드온이 있으면 lan-https 이미지도 받습니다.
  • docker compose … up -d --pull never --wait를 실행합니다. 같은 프로젝트, 볼륨, 자격 증명을 재사용하며, down, 볼륨 삭제, 자격 증명 교체는 절대 하지 않습니다.

--dry-run은 저장된 이미지 참조를 검증하고 사용할 Compose 파일을 나열할 뿐 Docker를 호출하지 않습니다. 다른 비공개 오버라이드(예: oidc-only.yml)는 자동으로 찾지 않으므로, 매번 명시적인 Compose 명령으로 지정하세요.

로그 확인, 중지, 재시작에는 래퍼와 같은 프로젝트, env 파일, Compose 파일을 사용합니다. Bash에서는 다음과 같습니다.

Terminal window
REPO=/absolute/path/to/Chorus
PROJECT=chorus-local
ENV_FILE="$HOME/.config/chorus-self-host/$PROJECT/env"
dc() {
docker compose --project-name "$PROJECT" --env-file "$ENV_FILE" \
-f "$REPO/deploy/self-host/compose.yml" \
-f "$REPO/deploy/self-host/compose.local.yml" "$@"
}
dc ps
dc logs --tail 100 app db

설치에서 lan-https.yml 같은 비공개 오버라이드를 쓰고 있다면 "$@" 앞에 -f "$HOME/.config/chorus-self-host/$PROJECT/lan-https.yml"(및 다른 오버라이드)를 추가하세요. 검증에는 dc config --quiet를 쓰고, 비밀번호를 출력하는 마스킹 없는 docker compose config는 쓰지 마세요.

HTTPS는 옵트인이며 비밀번호 로그인에는 필요하지 않습니다. 설치 중 HTTPS 질문에 yes라고 답하고 클라이언트가 사용할 호스트 이름이나 IPv4 주소를 입력하거나, 옵션으로 활성화합니다.

Terminal window
bash install.sh --target local --image-source build --https-host 192.0.2.10

예시 주소는 호스트의 실제 LAN IP나 이미 호스트로 해석되는 DNS 이름으로 바꾸세요. --https-port로 기본 포트 18638을 바꿀 수 있습니다(HTTP 포트와 달라야 합니다). --https는 호스트 이름 질문을 켜며, 비대화형 설치에는 --https-host나 CHORUS_HTTPS_HOST가 필요합니다. --no-https는 환경 변수의 선택을 덮어씁니다. IPv6 리터럴, 와일드카드 이름, URL은 거부됩니다.

무엇을 노출하는가. 설치는 Nginx 서비스 lan-https를 추가하고 앱·데이터베이스와 같은 Compose up에서 시작합니다. 0.0.0.0:18638에서 수신하여 내부 Compose 네트워크의 http://app:8637로 전달합니다. 응답 쿠키에 Secure를 붙이고, 전달 헤더와 WebSocket 업그레이드 헤더를 유지하며, Server-Sent Events가 스트리밍되도록 프록시 버퍼링을 끕니다. 평문 HTTP 리스너는 그대로 쓸 수 있고, Cloudflare Tunnel 오리진도 내부 HTTP로 유지됩니다. 설치는 DNS, 라우터, 방화벽 규칙을 바꾸지 않으므로 두 포트 모두 신뢰할 수 있는 네트워크로 제한하세요.

TLS 자료의 위치. 설치는 선택한 호스트를 SAN에 포함한 30일 유효 RSA 자체 서명 인증서를 생성하여 비공개 디렉터리의 lan-tls/server.crt와 lan-tls/server.key에 저장합니다. 그 경로와 HTTPS 포트는 비공개 env(CHORUS_TLS_CERT_FILE, CHORUS_TLS_KEY_FILE, CHORUS_HTTPS_PORT)에 저장되고, compose.lan-https.yml은 비공개 lan-https.yml로 복사됩니다. 파일은 읽기 전용으로 마운트되며, 파일이 없으면 시작이 실패합니다. 재배포는 이를 재사용하며 인증서를 다시 생성하지 않습니다.

이 엔드포인트로 OIDC를 쓰려면 정확한 콜백(예: https://<호스트 LAN IP>:18638/login/callback)을 ID 공급자에 등록하고 기존 콜백은 유지하세요. 쿠키는 포트가 아니라 호스트 단위이므로 같은 호스트의 HTTP와 HTTPS는 세션을 공유합니다. 로그인한 뒤에는 일관되게 HTTPS를 사용하세요.

갱신. 30일 만료 전, 또는 호스트 이름이나 IP가 바뀐 뒤에는 TLS 파일을 백업하고, SAN이 일치하는 새 인증서와 키를 같은 경로에 쓴 다음, 전체 Compose 명령으로 프록시만 다시 만듭니다: up -d --no-deps --force-recreate lan-https. Nginx를 재시작하는 것만으로는 교체한 파일이 반영되지 않습니다.

비활성화. 전체 Compose 명령으로 stop lan-https를 실행한 뒤 rm -f lan-https를 실행하고, 그다음에 비공개 lan-https.yml을 비공개 디렉터리 밖으로 옮깁니다. 이 파일이 없어지면 래퍼는 더 이상 프록시를 포함하지 않습니다. 롤백을 위해 인증서는 보관하세요.

기존 설치에 추가하기. 설치를 다시 실행하지 마세요. OpenSSL로 체크아웃 밖에 인증서와 키를 만들고, 비공개 env에 CHORUS_TLS_CERT_FILE, CHORUS_TLS_KEY_FILE, CHORUS_HTTPS_PORT를 설정한 뒤, deploy/self-host/compose.lan-https.yml을 비공개 디렉터리의 lan-https.yml로 복사하고 래퍼를 실행합니다(앱도 재배포됩니다).

호스트 포트를 열지 않고 HTTPS로 공개하려면 compose.local.yml 대신 compose.tunnel.yml을 사용합니다. 두 기본 모드를 함께 쓰지 마세요. LAN HTTPS 애드온은 따로 얹을 수 있습니다. Tunnel은 호스트 포트를 게시하지 않으며 TLS는 Cloudflare에서 종료됩니다.

Cloudflare 계정, 도메인, 원격 관리형 Named Tunnel은 직접 준비해야 하며 Chorus가 만들어 주지 않습니다. Cloudflare 대시보드에서 게시된 애플리케이션 호스트 이름을 추가하고, 그 오리진 서비스를 localhost가 아니라 http://app:8637로 설정합니다.

  1. 커넥터 토큰을 비공개 파일에 저장합니다(예: 비공개 디렉터리의 tunnel-token, 0700 디렉터리 안에서 모드 0444). 셸 인수나 기록에 남기지 마세요.
  2. 비공개 env에서 CHORUS_PUBLIC_URL을 외부 https:// 오리진(끝 슬래시 없음)으로, CLOUDFLARED_IMAGE를 고정한 cloudflare/cloudflared 릴리스(2025.4.0 이상)로, CLOUDFLARED_TOKEN_FILE을 토큰 파일의 절대 경로로 설정합니다.
  3. 롤백을 위해 현재의 완전한 dc 정의를 보관하세요. 터널 호출을 준비하고 검증한 뒤 커넥터 이미지만 먼저 가져오고 로컬 스택을 중지합니다. 설치된 앱과 데이터베이스 이미지를 재사용하므로 공개되지 않은 로컬 빌드에도 적용됩니다.

데이터베이스 볼륨과 인증 오버라이드는 유지하세요. 이 예시는 기존 pg17.yml과 oidc-only.yml을 포함한 뒤 터널 정책을 마지막에 적용합니다. https.yml이나 lan-https.yml은 자동으로 가져오지 마세요. 다른 사용자 설정은 확인 후 필요한 비노출 설정만 별도로 유지하고, 호스트 포트 매핑이나 터널 URL·쿠키 정책 변경은 포함하지 마세요.

Terminal window
PRIVATE_DIR="$HOME/.config/chorus-self-host/$PROJECT"
tunnel_dc() {
local compose_files=(-f "$REPO/deploy/self-host/compose.yml")
local override
for override in pg17.yml oidc-only.yml; do
if [ -f "$PRIVATE_DIR/$override" ]; then
compose_files+=(-f "$PRIVATE_DIR/$override")
fi
done
compose_files+=(-f "$REPO/deploy/self-host/compose.tunnel.yml")
docker compose --project-name "$PROJECT" --env-file "$ENV_FILE" \
"${compose_files[@]}" "$@"
}
tunnel_dc config --quiet &&
tunnel_dc pull cloudflared &&
dc down &&
tunnel_dc up -d --pull never --wait

전환에 성공하면 이후 모든 dc 명령에 터널 호출을 사용하세요.

Terminal window
dc() { tunnel_dc "$@"; }

커넥터는 tunnel --no-autoupdate run --token-file /run/secrets/tunnel_token을 실행하고, 이 오버라이드는 NEXTAUTH_URL을 CHORUS_PUBLIC_URL로, COOKIE_SECURE를 true로 설정합니다. 저장된 래퍼는 항상 compose.local.yml을 사용하므로, Tunnel 모드 스택은 이 명시적인 dc 명령으로 관리하세요. 되돌릴 때는 먼저 tunnel 버전 함수로 down한 다음 로컬 버전으로 up합니다. down -v는 절대 쓰지 마세요.

Cloudflare 쪽에서는 다음을 확인합니다.

  • Chorus 호스트 이름 전체, 최소한 인증된 페이지, 로그인 경로, /api/*, /api/mcp, /api/events, /api/events/notifications에서 캐시를 우회합니다.
  • 전체 경로에서 Server-Sent Events가 차례로 도착하는지 확인합니다.
  • 대화형 Cloudflare Access 챌린지는 API, MCP, 데몬 요청을 HTML이나 리디렉션으로 바꿉니다. Chorus API 키만으로는 Access를 통과하지 못합니다.
  • Tunnel 토큰은 비밀로 유지하고, 유출되면 Cloudflare에서 교체합니다.

구성 예: LAN HTTPS와 별도의 Cloudflare 커넥터

섹션 제목: “구성 예: LAN HTTPS와 별도의 Cloudflare 커넥터”
고급 구성: LAN 접속을 유지하면서 별도 커넥터로 공개

단일 호스트에서 검증된 구성 중 하나입니다. LAN에는 로컬 모드를 그대로 쓰고, compose.tunnel.yml로 바꾸지 않은 채 같은 앱을 Cloudflare로도 공개합니다. 컨테이너는 네 개입니다.

컨테이너출처역할
<project>-app-1compose.yml + compose.local.ymlChorus, 호스트 포트 18637(컨테이너 8637)
<project>-db-1compose.yml(마이그레이션 후에는 비공개 pg17.yml도)PostgreSQL 17, 내부 전용
<project>-lan-https-1비공개 LAN HTTPS 오버라이드Nginx, 호스트 포트 18638에서 http://app:8637로 전달
chorus-cloudflare별도의 docker runCloudflare 커넥터, 공개 호스트 이름을 http://app:8637로 전달

설치 프로그램은 env, lan-https.yml, lan-tls/를 비공개 디렉터리에 둡니다. default_deploy_local.sh는 CHORUS_DEPLOY_CONFIG로 다른 위치를 지정하지 않으면 체크아웃에 저장됩니다. 마이그레이션 후에는 비공개 pg17.yml로 새 데이터베이스 볼륨을 선택할 수 있습니다. 수동 dc 함수에도 래퍼와 같은 오버라이드를 포함하세요.

구성 순서:

  1. 위 설명대로 LAN HTTPS를 켠 로컬 모드로 설치하고 http://<host>:18637/api/health와 https://<host>:18638/api/health를 모두 확인합니다.
  2. Cloudflare 대시보드에서 터널의 공개 호스트 이름의 오리진 서비스를 http://app:8637 으로 설정합니다.
  3. 공개하기 전에 비공개 lan-https.yml의 app.environment에서 NEXTAUTH_URL을 공개 https:// 오리진으로 지정하고 COOKIE_SECURE: "true"를 설정하되 기존 프록시 서비스는 유지하세요. oidc-only.yml 등 사용자 설정이 있다면 완전한 Compose 호출로, 그렇지 않으면 래퍼로 재배포한 뒤 LAN HTTPS 로그인을 먼저 확인합니다.
  4. 터널 절과 같이 커넥터 토큰을 비공개 파일에 저장한 뒤, 커넥터를 스택의 기본 네트워크 <project>_default에 연결합니다.
Terminal window
docker run -d --name chorus-cloudflare --restart unless-stopped \
--network "${PROJECT}_default" \
-v "$HOME/.config/chorus-self-host/$PROJECT/tunnel-token:/run/secrets/tunnel_token:ro" \
cloudflare/cloudflared:<pinned-version> \
tunnel --no-autoupdate run --token-file /run/secrets/tunnel_token

이 구성을 쓰기 전에 다음 절충점을 알아 두세요.

  • 커넥터는 Compose 밖에 있습니다. 래퍼, dc ps, dc down은 커넥터를 관리하지 않습니다. dc down 전에 docker stop chorus-cloudflare와 docker rm chorus-cloudflare를 실행하세요. down이 <project>_default를 삭제하므로 dc up 후에 위의 docker run을 다시 실행합니다. 커넥터만 멈추면 공개 호스트 이름만 오프라인이 되고 LAN 접속은 계속됩니다.
  • 토큰을 명령줄에 두지 마세요. --token <value> 인수는 docker inspect와 프로세스 목록에 보입니다. cloudflared 2025.4.0 이상의 --token-file을 쓰고, 토큰을 인수로 넘긴 적이 있다면 Cloudflare에서 교체하세요.
  • 호스트 포트는 계속 공개됩니다. 터널 모드와 달리 18637과 18638이 0.0.0.0에 열려 있으므로 Docker를 인식하는 방화벽 규칙으로 신뢰할 수 있는 네트워크에만 허용하세요.
  • HTTPS 로그인이 필수입니다. 커넥터는 LAN 프록시를 우회하므로 앱 자체에서 Secure 쿠키를 설정해야 합니다. 이 재정의는 18637의 일반 HTTP 로그인을 비활성화합니다. LAN HTTPS나 공개 HTTPS 호스트 이름을 사용하세요.
  • 터널 절의 Cloudflare 캐시, Server-Sent Events, Access 관련 안내는 그대로 적용됩니다.

PostgreSQL 17과 기존 PostgreSQL 16 볼륨

섹션 제목: “PostgreSQL 17과 기존 PostgreSQL 16 볼륨”

새 설치는 postgres:17-alpine을 사용합니다. deploy/self-host/와 저장소 루트의 docker-compose.yml 모두 마찬가지입니다.

저장소에는 명시적인 오프라인 마이그레이션 헬퍼 deploy/self-host/migrate-pg16-to-17.sh가 있으며, 자세한 내용은 docs/POSTGRES_UPGRADE.md(중국어: docs/POSTGRES_UPGRADE.zh.md)에 있습니다. 요약하면 다음과 같습니다.

  1. postgres:16-alpine과 postgres:17-alpine을 미리 받아 둡니다. 헬퍼는 이미지를 받지 않습니다.
  2. 배포 설정과 앱 서명 자료를 백업합니다. 모든 쓰기 주체(앱, CLI, 개발 서버)와 원본 데이터베이스를 중지합니다. 설치 프로그램이 관리하는 인스턴스는 전체 Compose 명령으로 app과 db를 중지하고, 설치 프로그램을 다시 실행하지 마세요.
  3. 프로젝트의 데이터베이스 컨테이너에 docker inspect를 실행해 원본 볼륨 이름을 찾고, 데이터베이스 역할 비밀번호(Chorus 로그인 비밀번호가 아님)를 모드 0600인 비공개 파일에 씁니다.
  4. 먼저 --dry-run으로 헬퍼를 실행한 뒤, 이를 빼고 다시 실행합니다. 백업 디렉터리는 아직 없어야 하고 대상 이름은 사용 중이 아니어야 합니다.
Terminal window
bash deploy/self-host/migrate-pg16-to-17.sh \
--source-volume YOUR_OLD_PG16_VOLUME --target-volume chorus-pg17-migrated \
--backup-dir /absolute/private/path/pg17-backup \
--password-file /absolute/private/path/db-password --writers-stopped --dry-run

헬퍼는 원본 볼륨을 쓰기 가능으로 마운트하지 않습니다. 중지된 PG16 클러스터를 작업 볼륨으로 복사하고, 격리된 PG16 컨테이너에서 덤프한 뒤 새 PG17 볼륨에 복원하고, public 테이블 행 수와 시퀀스 상태를 비교합니다. 성공하든 실패하든 모든 볼륨과 백업을 남깁니다. 기본 레이아웃에 public 스키마를 쓰는 단일 데이터베이스만 지원합니다.

  1. 설치 프로그램이 관리하는 인스턴스에서는 비공개 디렉터리의 비공개 pg17.yml로 새 볼륨을 가리키고, 비공개 env에 POSTGRES_IMAGE=postgres:17-alpine을 설정합니다.
volumes:
db-data:
external: true
name: chorus-pg17-migrated

먼저 같은 앱 이미지로 데이터베이스를 검증하세요. 그 뒤로 래퍼는 매번 pg17.yml을 포함합니다. 새 쓰기가 생기기 전에 롤백하려면 원본 볼륨과 PG16 이미지를 함께 다시 선택해야 합니다. PG17이 쓰기를 받은 뒤에 되돌리면 그 쓰기는 사라집니다.

  1. dc ps에서 app과 db(활성화했다면 lan-https도)가 healthy 상태인지 확인합니다.

  2. 헬스 엔드포인트가 데이터베이스 상태를 보고하는지 확인합니다.

    로컬 모드에서는 선택한 포트를 사용하세요. 호스트 포트를 공개하지 않는 터널 모드에서는 공개 HTTPS 오리진을 사용하도록 BASE_URL을 설정합니다.

    Terminal window
    BASE_URL=http://localhost:18637
    curl --fail --silent --show-error "$BASE_URL/api/health"

    status: ok와 database: connected가 나와야 합니다.

  3. 브라우저에서 로그인해 일회용 프로젝트와 아이디어를 만들고, 새로 고친 뒤 남아 있는지 확인합니다. HTTPS에서는 세션 쿠키에 Secure와 HttpOnly가 붙어 있는지 확인합니다.

  4. 슈퍼 관리자 계정으로 /admin에 로그인합니다.

  5. 선택 사항으로, 테스트용 에이전트 API 키를 만들고 클라이언트에서 CHORUS_URL을 이 오리진으로 설정한 뒤 chorus mcp whoami를 실행합니다. chorus CLI를 참고하세요.

  6. dc up -d --force-recreate app을 실행하고 여전히 로그인되어 있는지 확인합니다. 이로써 app-data의 서명 키가 유지됨을 확인할 수 있습니다.

데이터베이스는 db-data에, /app/data(생성된 서명 키 포함)는 app-data에 있습니다. dc down은 둘 다 유지하지만 dc down -v는 이를 삭제합니다. 유지 보수 시간에 앱을 중지하고, 논리 덤프를 만들고, app-data를 보관하고, 비공개 env를 복사합니다.

Terminal window
(
set -euo pipefail
umask 077
BACKUP_DIR="$HOME/chorus-backups/$PROJECT/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
dc stop app
dc exec -T db pg_dump -U chorus -d chorus -Fc > "$BACKUP_DIR/database.dump"
dc run --rm --no-deps -T --entrypoint tar app \
-C /app/data -czf - . > "$BACKUP_DIR/app-data.tar.gz"
cp "$ENV_FILE" "$BACKUP_DIR/env"
dc start app
)

서브셸은 어느 단계든 실패하면 멈추며, 일부 파일만 있는 디렉터리는 유효한 백업이 아닙니다. 원인을 해결하고 백업을 완료한 뒤 업그레이드하세요. 앱이 계속 멈춰 있다면 오류를 처리한 다음 명시적으로 dc start app을 실행하세요. 복원 시 인증과 저장소 구성을 유지할 수 있도록 Compose 오버라이드와 이미지 버전도 보관하세요.

백업 디렉터리는 비공개(0700)로 유지하고, 다른 호스트로 보내기 전에 암호화하세요. 복원 리허설은 새 Compose 프로젝트와 포트에서 하고, 원래 프로젝트를 덮어쓰지 마세요. 원본 가이드에 복원 리허설이 자세히 나와 있습니다.

증상확인할 것
비공개 디렉터리, 스크립트, 프로젝트 리소스가 이미 있다고 나옴기존 설치입니다. 그 래퍼를 실행하거나, 별도 인스턴스라면 새 --project와 CHORUS_DEPLOY_CONFIG를 선택하세요.
Compose가 변수 누락을 보고함비공개 절대 경로 --env-file을 쓰고, DEFAULT_USER와 DEFAULT_PASSWORD를 비우지 말고, 기본 모드는 하나만 선택하세요.
포트가 이미 사용 중다른 --port나 --https-port를 선택하세요. 기존 볼륨을 지워서 해결하지 마세요.
PostgreSQL 17로 바꾼 직후 db가 실패볼륨에 아직 PG16 데이터가 있습니다. 그대로 두고 위의 마이그레이션을 따르세요.
env를 수정한 뒤 데이터베이스 인증 실패기존 볼륨은 이전 역할 비밀번호를 유지하며, POSTGRES_PASSWORD를 바꿔도 변하지 않습니다.
HTTP 로그인이 즉시 실패로컬 모드에는 COOKIE_SECURE=false(compose.local.yml에서 설정)가 필요합니다. 이를 true로 설정하는 비공개 https.yml이 있으면 LAN HTTP 로그인이 막힙니다.
한 달 뒤 브라우저가 HTTPS 엔드포인트를 거부30일 인증서가 만료되었습니다. 갱신한 뒤 lan-https를 다시 만드세요.
OIDC가 redirect_uri_mismatch로 실패포트를 포함한 정확한 HTTPS 콜백을 공급자에 등록하세요.
재생성 후 사용자가 로그아웃됨app-data 볼륨이나 명시적 NEXTAUTH_SECRET이 바뀌었습니다.
Tunnel은 healthy인데 호스트 이름이 오류게시된 호스트 이름, http://app:8637 오리진, 앱 상태, 토큰 파일 읽기 가능 여부를 확인하세요.
CLI가 Tunnel 호스트 이름에서 HTML을 받음Access 챌린지, 캐시나 Worker 규칙, 또는 잘못된 /api/mcp URL입니다.

문제를 해결하는 동안 env 파일, 토큰, 쿠키, 마스킹하지 않은 로그를 공유하지 말고, 호스트 전체에 영향을 주는 Docker prune 명령을 실행하지 마세요.