콘텐츠로 이동

서버 설치

이 저장소에서 서버 이미지 두 개를 빌드하고 서버 env 파일을 작성한 뒤 Docker Compose로 스택을 올리는 절차.

스택은 컨테이너 두 개로 구성된다. cctraced(OTLP 수집, 동기화 API, 대시보드)와 TimescaleDB다. 두 이미지 모두 레지스트리에 게시되지 않는 로컬 빌드 태그이고 compose 파일이 pull_policy: never를 지정하므로, 두 이미지를 먼저 빌드하지 않으면 스택이 시작되지 않는다.

서버 배포: 공개 포트와 서비스 서버 호스트에서 docker compose가 cctraced와 TimescaleDB를 실행한다. cctraced는 기본값으로 OTLP gRPC 4317과 OTLP HTTP 4318을 모든 인터페이스(0.0.0.0)에, HTTP 8080을 루프백(127.0.0.1)에 공개하고, TimescaleDB는 5432를 루프백에만 공개한다. Claude Code는 OTLP gRPC를 4317로, Codex는 OTLP HTTP를 4318로 보내고, cctrace 클라이언트의 세션 업로드와 브라우저의 대시보드 접속은 HTTP 8080을 쓰며, 다른 머신의 클라이언트는 HTTP_BIND를 바꾸거나 TLS 프록시를 거쳐야 접근할 수 있다. 선택 TLS 오버레이는 8443, 5317, 5318을 공개하는 Caddy 서비스를 추가하고, Caddy는 compose 네트워크를 통해 cctraced로 프록시한다. 클라이언트 머신 서버 호스트 Docker Compose 프록시 Claude Code 에이전트 Codex CLI 에이전트 cctrace 세션 동기화 브라우저 대시보드 OTLP gRPC :4317 · 0.0.0.0 OTLP HTTP :4318 · 0.0.0.0 HTTP :8080 127.0.0.1 HTTP_BIND로 변경 cctraced 서버 TimescaleDB 저장소 127.0.0.1:5432 Caddy 선택 TLS :8443 :5317 :5318

요구 사항

  • Compose 플러그인이 포함된 Docker
  • 이 저장소의 클론. 아래 모든 명령은 저장소 루트에서 실행

1. 이미지 빌드

$ docker build -t cctrace/timescaledb-pgmq:latest docker/timescaledb-pgmq/
$ docker build -f deploy/Dockerfile --build-arg UPDATE_SIGNING=optional \
    -t cctrace/cctraced:latest .
  • 첫 번째 이미지: pgmq 확장을 포함한 TimescaleDB
  • 두 번째 이미지: Docker 안에서 대시보드와 cctraced 바이너리를 빌드. UPDATE_SIGNING=optional은 업데이트 서명 키 없이 빌드. 기본값 required는 키가 없으면 실패
  • 서버 env 파일에 IMAGE_TAG 또는 DB_IMAGE_TAG를 지정했다면 정확히 같은 태그로 빌드(cctrace/cctraced:<IMAGE_TAG>, cctrace/timescaledb-pgmq:<DB_IMAGE_TAG>). Compose는 레지스트리로 대체하지 않음

2. 서버 env 파일 작성

Compose는 deploy/ 디렉터리의 .env(서버 env 파일)에서 설정을 읽는다. 예제 파일을 복사한 뒤 아래 3개 키만 고친다. 나머지 키는 기본값과 설명 주석 그대로 둔다.

$ cp deploy/.env.example deploy/.env
키 필수인 이유
JWT_SECRET 그대로 둘 기본값 없음. 없으면 Compose가 시작 거부, 32바이트 미만이면 cctraced 종료. openssl rand -hex 32로 생성
LOGS_DIR 기본값 없음. 없으면 Compose가 시작 거부. 컨테이너의 /data/logs에 마운트되는 호스트 디렉터리. 미리 생성 권장
DB_PASSWORD change-me-strong-password 자리 표시 값이 기본값이며 그대로 두면 안 됨. openssl rand -hex 20으로 생성

deploy/.env의 3개 키는 에디터로 고치거나, 명령줄에서 바로 바꾼다.

$ jwt_secret=$(openssl rand -hex 32) && sed -i.bak "s|^JWT_SECRET=.*|JWT_SECRET=${jwt_secret}|" deploy/.env
$ db_password=$(openssl rand -hex 20) && sed -i.bak "s|^DB_PASSWORD=.*|DB_PASSWORD=${db_password}|" deploy/.env
$ LOGS_DIR=/absolute/path/on/the/host/for/logs && mkdir -p "$LOGS_DIR" && sed -i.bak "s|^LOGS_DIR=.*|LOGS_DIR=${LOGS_DIR}|" deploy/.env
$ rm deploy/.env.bak

DB_PASSWORD는 첫 시작 전에 결정

PostgreSQL은 데이터 볼륨을 처음 초기화할 때만 비밀번호를 적용한다. 이후 DB_PASSWORD를 바꾸면 DB 역할은 옛 비밀번호로 남고 cctraced는 password authentication failed로 재시작을 반복한다. 복구하려면 DB 볼륨을 지워야 하는데 그러면 수집한 데이터가 모두 삭제된다.

DB_APP_CREDENTIALS도 첫 시작 전에 지정한다. <user>:<password>를 넣으면 첫 시작 때 비슈퍼유저 역할이 만들어지고 cctraced가 그 역할로 접속한다. 비워 두면 cctraced는 DB 슈퍼유저로 접속한다. 역할은 첫 시작 때만 만들어지며 이름은 cctrace일 수 없다. 비밀번호는 DB_PASSWORD처럼 openssl rand -hex 20으로 만든다. compose가 이 값을 연결 URL에 그대로 넣으므로 /, #, ?, %가 들어가면 URL이 깨진다.

$ app_password=$(openssl rand -hex 20) && sed -i.bak "s|^DB_APP_CREDENTIALS=.*|DB_APP_CREDENTIALS=cctrace_app:${app_password}|" deploy/.env
$ rm deploy/.env.bak

나머지 이름 규칙, 첫 시작에서 역할이 만들어지지 않았을 때의 조치, 이미 사용 중인 DB의 경우는 설정 참조.

3. 노출 범위 확인

첫 시작 전에 각 포트가 어느 인터페이스에 바인드될지 정한다.

포트 기본 호스트 바인드 용도
8080 127.0.0.1 대시보드와 REST API(클라이언트 동기화 포함)
4317 0.0.0.0(모든 인터페이스) OTLP over gRPC
4318 0.0.0.0(모든 인터페이스) OTLP over HTTP
5432 127.0.0.1(고정) TimescaleDB

OTLP는 TLS 없이 모든 인터페이스에서 수신

4317과 4318 포트는 다른 머신의 텔레메트리를 받는다. cctraced는 TLS를 종료하지 않는다. 통제하지 않는 네트워크라면 서버 env 파일에서 GRPC_BIND와 OTEL_HTTP_BIND를 루프백이나 사설 주소로 지정하거나, 방화벽·VPN으로 포트를 제한하거나, HTTPS 오버레이를 쓴다.

대시보드는 기본적으로 루프백에 바인드된다. 클라이언트 머신이 동기화하려면 8080 포트에 닿아야 하므로 HTTP_BIND를 도달 가능한 주소로 지정하거나 TLS를 종료하는 프록시를 앞에 둔다. 전체 포트 표는 포트 참조.

4. 스택 시작

$ docker compose --env-file deploy/.env -f deploy/docker-compose.yml up -d

up -d가 정상 종료했다는 것은 컨테이너가 생성됐다는 뜻일 뿐 서버가 시작됐다는 뜻은 아니다. cctraced의 설정 오류는 자체 로그에만 나온다.

5. 확인

$ docker compose --env-file deploy/.env -f deploy/docker-compose.yml ps
$ curl -fsS http://127.0.0.1:8080/api/health

docker compose ps로 두 컨테이너가 healthy 상태이고 curl로 /api/health 확인에 성공한 터미널

  • 두 컨테이너 모두 Up과 (healthy) 표시가 정상. cctraced 헬스 체크는 /api/version을 확인하며 시작 유예가 30초라 처음에는 health: starting 표시
  • /api/health 응답: DB에 연결되면 "status":"ok", 연결되지 않으면 HTTP 503과 "status":"unhealthy"
  • cctraced가 Restarting이면 로그 확인:
$ docker compose --env-file deploy/.env -f deploy/docker-compose.yml logs cctraced

6. 첫 관리자 생성

사용자가 없는 DB에서는 cctraced가 로그에 일회용 설정 토큰을 출력한다.

[cctraced] initial administrator setup token: <token>

cctraced 로그에 찍힌 최초 관리자 설정 토큰 줄(토큰 값 가림)

토큰을 직접 정하려면 첫 시작 전에 서버 env 파일에 CCTRACE_SETUP_TOKEN을 지정한다. 첫 관리자가 생성되면 토큰은 무효가 된다. 이어서 첫 관리자로 진행한다.

선택: Caddy로 HTTPS 적용

deploy/docker-compose.tls.yml은 Caddy 컨테이너를 추가하는 선택형 오버레이다. Caddy가 세 리스너 모두의 TLS를 종료하고 deploy/caddy/Caddyfile에 따라 compose 네트워크를 통해 cctraced로 전달한다. 기본 compose 파일은 그대로이며 Caddy 이미지(caddy:2-alpine)는 Docker Hub에서 받는다.

  1. 서버 env 파일에서 평문 포트를 루프백으로 옮기고 서버 이름 지정:

    HTTP_BIND=127.0.0.1
    GRPC_BIND=127.0.0.1
    OTEL_HTTP_BIND=127.0.0.1
    CADDY_SITE_ADDRESS=cctrace.company.example
    

    CADDY_SITE_ADDRESS는 오버레이 필수값이며 호스트 이름이나 서버 IP 주소 모두 가능. 바인드 설정 세 개를 빼면 평문 포트가 TLS 포트 옆에서 계속 열려 있음.

  2. compose 파일 두 개로 시작:

    $ docker compose --env-file deploy/.env \
        -f deploy/docker-compose.yml -f deploy/docker-compose.tls.yml up -d
    
채널 평문(컨테이너) TLS(호스트 기본값)
대시보드와 REST API 8080 8443
OTLP gRPC 4317 5317
OTLP HTTP 4318 5318

이후 클라이언트는 대시보드와 동기화에 https://cctrace.company.example:8443, OTLP에 5317 또는 5318 포트를 사용한다.

Codex는 OTLP/HTTP로 보내므로 cctrace는 프로필의 OTEL 엔드포인트에서 4317을 4318로 바꾸듯 5317을 5318로 바꿔 Codex 메트릭 엔드포인트를 만든다. 내부 CA를 쓰면 Codex 설정에도 그 CA가 있어야 한다. 아래와 Codex CLI 참조.

인증서

CADDY_TLS_MODE 기본값은 internal이다. Caddy가 자체 CA로 인증서를 발급하므로 공개 DNS가 필요 없고 IP 주소만으로도 동작한다. 클라이언트는 이 CA를 신뢰해야 한다. CA 내보내기:

$ docker compose --env-file deploy/.env \
    -f deploy/docker-compose.yml -f deploy/docker-compose.tls.yml \
    exec caddy cat /data/caddy/pki/authorities/local/root.crt > cctrace-ca.crt

이 파일을 각 클라이언트 머신에 복사해 cctrace에 한 번 지정한다. 엔드포인트가 https://이면 cctrace init이 묻고, 직접 지정해도 된다.

$ cctrace config set server.ca_cert_file ~/cctrace-ca.crt

OS 키체인에 이미 CA가 있어도 지정한다. 키체인만으로 Claude Code와 Codex에 충분한지는 시험하지 않았다. 클라이언트마다 CA를 받는 곳이 달라서 cctrace가 각각에 이 파일 하나를 넣는다.

클라이언트 CA를 읽는 곳 설정 주체
cctrace 시스템 루트 + server.ca_cert_file cctrace
Claude Code 설정 파일의 NODE_EXTRA_CA_CERTS, exporter는 5318로 http/protobuf(이유) cctrace
Codex [otel] 섹션의 tls = { ca-certificate = "..." } cctrace

실행 중인 동기화 데몬은 시작할 때의 CA를 계속 쓴다. CA를 지정하거나 해제한 뒤에는 데몬을 다시 시작한다(cctrace sync --stop 후 평소대로 시작). Codex 섹션은 다음 동기화 시작 때 다시 쓰인다. 클라이언트 설정 참조.

클라이언트 바이너리를 처음 curl로 내려받을 때는 이 설정이 쓰이지 않으므로 --cacert cctrace-ca.crt를 붙인다. 클라이언트 설치 참조.

이름이 공개 DNS로 해석되고 인터넷에서 80 포트에 닿을 수 있다면 CADDY_TLS_MODE에 ACME 계정 이메일 주소(예: ops@example.com)를 지정한다. acme라는 단어를 그대로 넣으면 Caddy가 시작하지 않는다. Caddy가 공개적으로 신뢰되는 인증서를 받으므로 cctrace와 Claude Code는 CA 설정이 필요 없다. Codex가 server.ca_cert_file 없이 공인 인증서로 보내는지는 측정하지 않았다.

caddy_data 볼륨 유지

발급한 인증서와 내부 CA가 이 볼륨에 있다. 지우면 새 CA가 만들어지고 이전 CA를 신뢰하던 클라이언트는 모두 연결이 끊긴다.

다음 단계

  • 설정: 서버 env 파일의 모든 설정
  • 운영: 로그, 백업, 업그레이드, 보존 기간