Clowld Space Docs

자체 호스팅 빠른 시작

Docker Compose로 Clowld Space를 시작하고 로그인한 뒤 첫 실행 컴퓨터를 연결합니다.

자체 호스팅 Clowld Space는 두 부분으로 나뉩니다.

부분실행하는 항목설치 위치
Clowld Space 서비스Web, API, PostgreSQLDocker가 설치된 컴퓨터
실행 컴퓨터Clowld Space 데몬과 AI 코딩 도구개발자가 실제로 작업하는 컴퓨터

두 부분을 같은 컴퓨터에 둘 수도 있고 분리할 수도 있습니다. 자체 호스팅으로 대체하는 것은 Clowld Space 부분뿐입니다.

이 문서에서는 Docker Compose를 사용합니다. Kubernetes 배포는 저장소의 Self-hosting guide를 참고하세요.

시작하기 전에

Clowld Space 서비스를 실행할 컴퓨터에는 다음 항목이 필요합니다.

  • Docker Engine 또는 Docker Desktop, 실행 가능한 docker compose
  • Git, Make, curl, OpenSSL
  • 로컬 3000, 8080 port가 사용 중이지 않음

먼저 Docker와 Compose를 사용할 수 있는지 확인합니다.

docker info
docker compose version

Clowld Space는 Compose v2인 docker compose를 사용합니다. 이전 docker-compose v1은 지원하지 않습니다.

실행 컴퓨터에는 Claude Code, Codex, Cursor 같은 AI 코딩 도구가 하나 이상 설치되어 로그인된 상태여야 합니다. Clowld Space CLI는 5단계에서 설치합니다.

1. Clowld Space 시작

서비스를 실행할 컴퓨터에서 다음을 실행합니다.

git clone --depth 1 https://github.com/sooodasama/clowld-space.git
cd clowld-space
make selfhost

처음 실행할 때 make selfhost가 다음 작업을 수행합니다.

  1. .env.example에서 .env 생성
  2. JWT_SECRET, PostgreSQL 비밀번호, CLOWLD_VCS_SECRET_KEY(자체 호스팅 Git 연동 암호화 키)를 임의 생성
  3. PostgreSQL, Clowld Space backend, Clowld Space frontend 이미지 pull
  4. persistent data volume 생성 및 세 컨테이너 시작
  5. backend가 health check에 응답할 때까지 대기

이후 make selfhost를 다시 실행하면 기존 .env와 data volume을 계속 사용하며 키를 새로 만들지 않습니다.

make selfhost는 공개된 이미지를 pull하며 현재 checkout의 코드를 컴파일하지 않습니다. 로컬 소스 코드를 테스트하려면 make selfhost-build를 사용하세요.

공개된 이미지는 release tag를 따르지만, 단순한 git clone은 보통 릴리스보다 앞선 main을 checkout합니다. Clowld Space release tag는 clowld-vX.Y.Z 형식을 사용합니다. 이 checkout에서 CLI 또는 make selfhost-build 이미지를 빌드하려면 먼저 하나의 정확한 release tag를 선택하세요. Compose의 CLOWLD_SPACE_IMAGE_TAG와 Helm의 images.backend.tag / images.frontend.tag에도 같은 tag를 사용해야 소스, CLI, 실행 중인 이미지 버전이 일치합니다.

git fetch --tags --depth 1
git tag --list 'clowld-v*' --sort=-v:refname
# 목록에서 원하는 release를 고른 다음 checkout합니다:
git checkout clowld-vX.Y.Z

2. 서비스 준비 상태 확인

컨테이너 상태를 확인합니다.

docker compose -f docker-compose.selfhost.yml ps

postgres는 healthy, backend와 frontend는 실행 중으로 표시되어야 합니다. 그런 다음 backend, 데이터베이스, migration을 확인합니다.

curl -fsS http://localhost:8080/readyz

정상 응답:

{"status":"ok","checks":{"db":"ok","migrations":"ok"}}

Backend 컨테이너는 시작할 때마다 데이터베이스 migration을 먼저 실행하고 서비스를 시작하므로 migration 명령을 직접 실행할 필요가 없습니다.

3. 접근 방식 선택

로컬에서 접근

http://localhost:3000을 바로 엽니다. 이후 clowld setup self-host를 실행할 때도 URL을 전달할 필요가 없습니다.

원격에서 접근

Docker Compose는 기본적으로 3000과 8080을 127.0.0.1에만 bind합니다. 0.0.0.0으로 바꿔 공개 인터넷에 직접 노출하지 말고 HTTPS reverse proxy를 사용하세요.

다음 두 도메인을 예로 사용합니다.

  • app.example.com: Clowld Space Web
  • api.example.com: API, health check, 데몬 연결

먼저 .env에 공개 주소를 설정합니다.

FRONTEND_ORIGIN=https://app.example.com
CLOWLD_APP_URL=https://app.example.com
CLOWLD_PUBLIC_URL=https://api.example.com

이 설정에서는 모든 브라우저 트래픽이 app 도메인을 거치고 cookie가 도메인을 넘지 않으므로 COOKIE_DOMAIN을 설정할 필요가 없습니다. 브라우저에서 api 도메인에 직접 접근하게 바꾼다면 반드시 설정해야 합니다. 환경 변수를 참고하세요.

그런 다음 DNS를 설정하고 Caddy로 로컬 port를 proxy합니다.

app.example.com {
    # CLI setup은 정확한 /health 경로에 GET을 보내 200을 요구합니다.
    # /health를 전달하지 않는 구버전 web release에서도 동작하도록 직접 라우팅합니다.
    handle /health {
        reverse_proxy 127.0.0.1:8080
    }

    # 브라우저 WebSocket을 backend로 직접 전달
    @ws path /ws /ws/*
    handle @ws {
        reverse_proxy 127.0.0.1:8080 {
            flush_interval -1
        }
    }

    # 나머지 경로는 frontend로 전달하며 frontend가 API와 로그인 요청을 proxy
    handle {
        reverse_proxy 127.0.0.1:3000
    }
}

api.example.com {
    reverse_proxy 127.0.0.1:8080 {
        flush_interval -1
    }
}

모든 트래픽을 단일 오리진(하나의 도메인, 또는 한 호스트의 한 포트 — 소규모 서버에서 흔한 구성)으로 제공하려면 데몬이 의존하는 경로를 backend로 명시적으로 전달하고 나머지는 frontend가 처리하게 합니다.

clowld.example.com {
    # CLI 도달성 probe는 정확한 <server-url>/health 경로에 GET을 보내 200을 요구합니다.
    # 이 backend 직접 라우팅은 모든 web release에서 동작합니다.
    handle /health {
        reverse_proxy 127.0.0.1:8080
    }

    # 브라우저 realtime WebSocket; Upgrade 요청을 backend에서 처리합니다.
    handle /ws {
        reverse_proxy 127.0.0.1:8080 {
            flush_interval -1
        }
    }

    # 데몬 긴 연결 WebSocket: 데몬은 /ws가 아니라
    # {server-url}/api/daemon/ws로 연결합니다. 이 경로가 없으면 핸드셰이크
    # 실패 후 데몬이 조용히 폴링으로 폴백합니다.
    handle /api/daemon/ws {
        reverse_proxy 127.0.0.1:8080 {
            flush_interval -1
        }
    }

    # 나머지 경로는 frontend로 전달하며 frontend가 API와 로그인 요청을 proxy
    handle {
        reverse_proxy 127.0.0.1:3000
    }
}

단일 오리진 구성에서는 공개 URL 변수를 명시적으로 설정합니다.

FRONTEND_ORIGIN=https://clowld.example.com
CLOWLD_APP_URL=https://clowld.example.com
CLOWLD_PUBLIC_URL=https://clowld.example.com
# 선택 사항인 daemon API origin; 아래의 fallback을 사용하면 생략할 수 있습니다.
CLOWLD_DAEMON_SERVER_URL=https://clowld.example.com

그 다음 --server-url과 --app-url 모두에 https://clowld.example.com을 사용합니다. CLOWLD_PUBLIC_URL은 공개 webhook 및 callback URL용이며 daemon 전용 override가 아닙니다. 두 주소가 같다면 같은 origin으로 설정해도 됩니다. CLOWLD_DAEMON_SERVER_URL 줄은 생략할 수 있지만, 명시하면 daemon 연결 경로를 고정할 수 있습니다.

Caddy가 TLS 인증서를 발급하고 WebSocket을 전달합니다. .env를 수정한 뒤 up -d로 컨테이너를 다시 만들어 새 설정을 적용합니다.

docker compose -f docker-compose.selfhost.yml up -d
curl -fsS https://api.example.com/readyz
curl -fsS https://app.example.com/api/config | grep -o '"daemon_server_url":"[^"]*"'

단일 오리진 배포에서는 위 두 호스트를 모두 https://clowld.example.com으로 바꿉니다. 마지막 명령은 daemon이 API와 WebSocket에 연결할 때 사용하는 공개 URL인 daemon_server_url을 출력합니다. CLOWLD_DAEMON_SERVER_URL이 설정되어 있으면 이를 우선 사용하고, 다음으로 CLOWLD_PUBLIC_URL, 그 다음은 앱 URL(CLOWLD_APP_URL, 미설정이면 FRONTEND_ORIGIN)을 사용합니다. CLOWLD_APP_URL과 FRONTEND_ORIGIN이 모두 비어 있으면 daemon 또는 public URL 변수가 설정되어 있어도 필드가 완전히 생략됩니다. localhost가 출력되면 FRONTEND_ORIGIN과 CLOWLD_APP_URL을 공개 주소로 명시적으로 설정한 뒤 컨테이너를 다시 만드세요. /api/config는 인증 없이 접근할 수 있으므로 이 URL에 자격 증명이나 query-string secret을 넣지 마세요.

docker compose restart는 기존 컨테이너만 다시 시작하며 .env를 다시 읽지 않습니다. 설정을 수정한 뒤 .env를 다시 읽게 하려면 docker compose -f docker-compose.selfhost.yml up -d를 실행하세요.

4. 로그인 및 워크스페이스 만들기

로컬 http://localhost:3000 또는 앞에서 설정한 https://app.example.com을 열고 이메일을 입력해 인증 코드를 받습니다.

기본 설정에는 이메일 서비스가 없습니다. 인증 코드를 요청한 뒤 backend 로그에서 확인할 수 있습니다.

docker compose -f docker-compose.selfhost.yml logs backend \
  | grep "Verification code"

로그에는 다음과 비슷한 내용이 나타납니다.

[DEV] Verification code for you@example.com: 123456

인증 코드를 입력하고 첫 워크스페이스를 만듭니다. Resend 또는 SMTP를 설정하면 이메일로 인증 코드가 전달되므로 멤버가 컨테이너 로그를 읽을 필요가 없습니다. 자세한 내용은 로그인 및 가입 설정을 참고하세요.

자체 호스팅 환경은 기본적으로 APP_ENV=production을 사용하므로 고정 인증 코드가 활성화되지 않습니다. 공개 인터넷 인스턴스에 CLOWLD_DEV_VERIFICATION_CODE를 설정하지 마세요.

5. 실행 컴퓨터 연결

다음 명령은 Docker 서버가 아니라 AI 코딩 도구를 실행할 컴퓨터에서 실행합니다. 두 컴퓨터가 같을 수도 있습니다.

작업은 데몬을 실행하는 사용자의 모든 권한으로 동작하며, 그 사용자가 읽고 쓸 수 있는 모든 것에 접근할 수 있습니다. 개인 계정 대신 전용 Unix 사용자, 컨테이너, 또는 VM에서 데몬을 실행하세요. 보안 모델을 참고하세요.

먼저 Clowld Space CLI를 설치합니다.

macOS / Linux

curl -fsSL https://updates.clowld.xyz/cli/install.sh | bash

Windows PowerShell

irm https://updates.clowld.xyz/cli/install.ps1 | iex

Clowld Space 서비스가 같은 컴퓨터에 있다면 다음을 실행합니다.

clowld setup self-host

Clowld Space 서비스가 다른 컴퓨터에 있다면 앞에서 설정한 두 주소를 전달합니다.

clowld setup self-host \
  --server-url https://api.example.com \
  --app-url https://app.example.com

이 명령은 먼저 정확한 <server-url>/health 경로를 확인해 HTTP 200을 요구한 뒤 브라우저를 열어 로그인을 완료합니다. 로그인 후 로컬 자격 증명을 저장하고 데몬을 시작합니다. 이 단계에서 Server not reachable 오류가 나면 probe가 200을 받지 못한 것입니다. /health를 전달하지 않는 구버전 web release로 프록시가 보내고 있거나, 프록시가 backend로 라우팅하지 않거나, DNS·TLS·방화벽·타임아웃·backend 5xx 같은 하위 계층 문제가 원인일 수 있습니다. 문제 해결을 참고하세요.

연결 상태를 확인합니다.

clowld daemon status

정상적인 출력에는 다음 내용이 표시됩니다.

  • Daemon: running
  • Agents에 로컬에 설치된 AI 코딩 도구 포함
  • Workspaces가 0보다 큼

6. 첫 실행 완료

Clowld Space로 돌아옵니다. 런타임 목록에 온라인 런타임이 나타나면 에이전트를 만들고 첫 태스크를 할당합니다.

실행 로그가 "완료됨"으로 바뀌고 타임라인에 에이전트의 답변이 나타나면 자체 호스팅 서비스, 실행 컴퓨터, AI 코딩 도구가 연결된 것입니다. 자세한 단계는 빠른 시작의 3~5단계를 참고하세요.

자주 사용하는 관리 명령

다음 명령은 모두 clowld 저장소 디렉터리에서 실행합니다.

# 상태 확인
docker compose -f docker-compose.selfhost.yml ps

# backend 로그 확인
docker compose -f docker-compose.selfhost.yml logs -f backend

# .env 변경 적용
docker compose -f docker-compose.selfhost.yml up -d

# 서비스를 중지하고 data volume 유지
docker compose -f docker-compose.selfhost.yml down

공개된 이미지 업그레이드:

git pull --ff-only
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d
curl -fsS http://localhost:8080/readyz

Docker Compose 설치를 업그레이드하는 방법은 두 가지이며, 기존 설치에서는 결과가 같습니다 — Makefile의 selfhost 타깃이 동일한 docker compose pull + up -d를 실행하고, 여기에 더해 .env가 없으면 생성하고 /health를 기다린 뒤 상태 요약을 출력합니다. 편한 쪽을 쓰세요.

cd clowld-space
git pull
make selfhost
cd clowld-space
git pull
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d

git pull이 실제로 하는 일

git pull이 갱신하는 것은 docker-compose.selfhost.yml 자체입니다 — 새로 추가된 환경 변수, 새 서비스, 변경된 healthcheck. 새 Clowld Space 버전을 받아오는 수단이 아닙니다.

실제로 어떤 버전이 뜰지는 docker compose pull이 결정합니다. 이 명령이 GHCR에 해당 태그가 지금 어떤 이미지를 가리키는지 물어보기 때문입니다. 그래서 몇 달 묵은 checkout에서도 오늘자 latest 이미지를 받을 수 있고, 반대로 git pull만 해서는 이미지를 받아 컨테이너를 다시 만들기 전까지 아무것도 바뀌지 않습니다.

CLOWLD_SPACE_IMAGE_TAG를 고정해 뒀다면 두 방법 모두 업그레이드되지 않습니다. 두 이미지 모두 ${CLOWLD_SPACE_IMAGE_TAG:-latest}로 해석되며(docker-compose.selfhost.yml의 services.backend.image와 services.frontend.image), .env.example에는 CLOWLD_SPACE_IMAGE_TAG=latest가 들어 있습니다. .env에서 특정 릴리스로 고정해 뒀다면 pull은 같은 태그를 다시 받아올 뿐이라 예전 버전에 그대로 머무릅니다 — 오류도, 경고도 없습니다. 업그레이드 전에 확인하세요.

grep CLOWLD_SPACE_IMAGE_TAG .env
# CLOWLD_SPACE_IMAGE_TAG=clowld-v1.0.5   ← 고정됨: 먼저 latest(또는 원하는 릴리스)로 수정

.env는 덮어쓰이지 않습니다

make selfhost는 .env 파일이 없을 때만 생성합니다. 기존 설치에서 다시 실행해도 JWT_SECRET, Postgres 비밀번호, 이메일 설정, FRONTEND_ORIGIN은 그대로 유지됩니다.

먼저 Postgres를 백업하세요

migration은 앞으로만 진행되고 되돌릴 수 없으므로, 중요한 배포를 업그레이드하기 전에 덤프를 받아 두세요.

docker compose -f docker-compose.selfhost.yml exec -T postgres \
  pg_dump -U clowld clowld > clowld-space-backup.sql && gzip clowld-space-backup.sql

pg_dump를 gzip으로 바로 파이프하지 마세요. 셸은 파이프라인에서 마지막 명령의 종료 상태를 반환하므로, pg_dump … | gzip > backup.sql.gz는 덤프가 실패해도 0으로 끝나면서 형식상 완전히 유효하지만 내용이 비어 있는 20바이트짜리 아카이브를 남깁니다. 먼저 파일로 리다이렉트하면 pg_dump 자신의 종료 상태가 적용되고, &&는 실제로 성공한 덤프만 압축합니다.

POSTGRES_USER / POSTGRES_DB를 기본값 clowld에서 바꿨다면 .env의 실제 값으로 교체하세요. 데이터는 clowld_pgdata라는 named volume에 있으며 docker compose down으로는 사라지지 않지만, down -v로는 사라집니다.

migration은 자동으로 실행됩니다

1단계와 마찬가지로, 백엔드 컨테이너는 트래픽을 받기 전에 시작 시점에 ./migrate up을 실행합니다(docker/entrypoint.sh). 별도의 업그레이드 명령은 없습니다 — 새 이미지를 띄우는 것 자체가 migration 단계입니다. 진행 상황을 보려면:

docker compose -f docker-compose.selfhost.yml logs -f backend

Migration은 backend 시작 시 자동으로 실행됩니다. migration 103처럼 이전 데이터 backfill이 필요한 migration도 자동으로 backfill을 완료합니다. 드물게 자동 backfill이 실패하고 refusing to drop legacy daily rollups 오류가 나타나면 문제 해결을 참고하세요.

/health 말고 /readyz로 검증하세요

/health는 liveness 프로브라서 프로세스가 살아 있기만 하면 {"status":"ok"}를 반환합니다 — migration이 실패했더라도 마찬가지입니다. /readyz(server/cmd/server/router.go에 등록된 readyHandler, /healthz는 별칭)는 데이터베이스와 적용된 migration 집합을 확인하므로, 잘못된 업그레이드를 잡아내는 것은 이쪽입니다.

curl -s localhost:8080/readyz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}

HTTP 200이면서 두 검사가 모두 ok가 아니라면 새 버전이 migration을 마치지 못한 것입니다. 트래픽을 보내기 전에 백엔드 로그를 확인하세요.

Kubernetes

Helm은 자체 업그레이드 경로가 있습니다. values 파일에서 images.backend.tag / images.frontend.tag를 원하는 릴리스로 설정한 뒤 helm upgrade를 실행하세요. 태그가 바뀌면 파드 스펙이 바뀌므로 Kubernetes가 새 이미지를 받아 Deployment를 롤아웃합니다 — 이것이 확실한 경로입니다.

kubectl -n clowld rollout restart는 그 자체로는 업그레이드가 아닙니다. 차트는 pullPolicy: IfNotPresent로 배포되므로(deploy/helm/clowld-space/values.yaml), 해당 태그를 이미 캐시한 노드는 예전 이미지를 재사용하고 restart를 해도 아무것도 바뀌지 않습니다 — CLOWLD_SPACE_IMAGE_TAG 고정과 같은 부류의 함정입니다. 부동 태그로 이 방식을 쓰려면 먼저 images.backend.pullPolicy / images.frontend.pullPolicy를 Always로 설정하세요. 저장소의 Self-hosting guide를 참고하세요.

docker compose down은 pgdata와 backend_uploads를 유지합니다. -v를 추가하면 데이터베이스를 포함한 이 data volume이 삭제됩니다. 인스턴스를 비우려는 것이 확실하지 않다면 docker compose down -v를 실행하지 마세요.

자주 묻는 문제

증상먼저 확인할 사항
/readyz가 ok를 반환하지 않음docker compose -f docker-compose.selfhost.yml logs backend postgres를 실행합니다.
인증 코드를 받지 못함인증 코드를 한 번 요청한 뒤 backend 로그에서 Verification code를 찾습니다.
setup self-host에 server 접근 불가가 표시됨실행 컴퓨터에서 https://api.example.com/health를 요청해 DNS, TLS, reverse proxy에 모두 접근할 수 있는지 확인합니다. 404가 반환되면 프록시가 /health를 이 경로를 전달하지 않는 구버전 web 릴리스로 보내고 있다는 뜻이니 backend로 직접 라우팅해야 합니다(문제 해결).
데몬의 Agents가 비어 있음AI 코딩 도구가 PATH에 있고 로그인되어 있는지 확인한 뒤 clowld daemon restart를 실행합니다.
태스크가 계속 큐에서 기다림clowld daemon status를 실행해 데몬이 실행 중이며 워크스페이스에 연결되었는지 확인합니다.

더 많은 경우는 문제 해결을 참고하세요.

다음 단계