---
id: M002
title: "Chrysopedia Deployment — GitHub, ub01 Docker Stack, and Production Wiring"
status: complete
completed_at: 2026-03-30T01:30:04.974Z
key_decisions:
  - D015: Subnet 172.32.0.0/24 for Chrysopedia network (172.24 taken by xpltd_docs)
  - D016: Ollama container for embedding model (nomic-embed-text) since OpenWebUI doesn't serve /v1/embeddings
  - D017: Compose directory uses symlink to repo's docker-compose.yml
  - D018: SSH agent forwarding for git clone on ub01 (no gh CLI)
key_files:
  - docker-compose.yml
  - docker/Dockerfile.api
  - alembic/env.py
  - .env.example
  - CLAUDE.md
  - README.md
lessons_learned:
  - Qdrant, Ollama, and nginx-alpine images lack common tools (curl, wget) — always verify healthcheck commands work inside the actual container image before deploying
  - Alembic env.py sys.path needs both ../backend/ (local dev) and ../ (Docker where backend code is at WORKDIR)
  - ZFS with Active Directory integration can cause uid mismatches (local uid 1000 vs AD uid 11103) — use sudo chown when needed
  - Celery workers need celery inspect ping for healthcheck, not HTTP — same Docker image but different entrypoint
  - SSH agent forwarding (-A flag) is required for git clone on servers without GitHub credentials
---

# M002: Chrysopedia Deployment — GitHub, ub01 Docker Stack, and Production Wiring

**Pushed codebase to GitHub (xpltdco/chrysopedia), deployed 7-container Docker stack to ub01 (all healthy on port 8096), ran Alembic migration, pulled embedding model, and established ub01 as the canonical development directory.**

## What Happened

M002 delivered the full deployment pipeline across 3 slices and 4 tasks.\n\n**S01** fixed the Docker Compose configuration: changed the network subnet from 172.24.0.0/24 (occupied by xpltd_docs) to 172.32.0.0/24, added Qdrant vector database and Ollama embedding services (bringing the stack to 7 containers), changed the web UI external port to 8096 following XPLTD port-suffix naming convention, and removed the API host port mapping (internal only via nginx proxy). Created the private GitHub repo xpltdco/chrysopedia and pushed the full codebase.\n\n**S02** deployed to ub01: cloned the repo (via SSH agent forwarding due to no gh CLI on server), created the /vmPool/r/compose/xpltd_chrysopedia/ directory with a symlink to the repo's docker-compose.yml, created all bind-mount service directories, wrote .env with production secrets (LLM API key, DB password), built all 3 Docker images. Fixed 4 healthcheck issues iteratively — Ollama image lacks curl (use `ollama list`), Qdrant image lacks wget/curl (use `bash /dev/tcp`), nginx-alpine's busybox wget fails on localhost (use curl), and the worker doesn't serve HTTP (use `celery inspect ping`). Fixed Alembic env.py sys.path for Docker layout where backend code is at /app/ not /app/../backend/. Ran the 001_initial Alembic migration creating all 7 tables. Pulled nomic-embed-text (274MB) into the Ollama container for embeddings.\n\n**S03** created CLAUDE.md in the dev directory redirecting future development to the ub01 canonical path (/vmPool/r/repos/xpltdco/chrysopedia), and updated README.md with a deployment section including service URLs, update workflow, and first-time setup instructions.

## Success Criteria Results

### 1. GitHub repo xpltdco/chrysopedia exists (private) ✅\n`gh repo view xpltdco/chrysopedia --json visibility` returns PRIVATE\n\n### 2. Code cloned to /vmPool/r/repos/xpltdco/chrysopedia on ub01 ✅\nAll files present, git log matches GitHub, git pull works with SSH agent forwarding\n\n### 3. docker compose up -d starts all 7 services healthy ✅\nAll 7 containers show healthy status: db, redis, qdrant, ollama, api, worker, web-8096\n\n### 4. Alembic migrations run successfully ✅\n`docker exec chrysopedia-api alembic current` returns `001_initial (head)`\n\n### 5. GET /health returns healthy ✅\n`curl http://localhost:8096/health` returns `{status:ok, database:connected}`\n\n### 6. Web UI accessible on ub01:8096 ✅\n`curl http://localhost:8096/` returns valid HTML with React app\n\n### 7. CLAUDE.md redirects future development ✅\nCLAUDE.md exists with clear redirect to ub01 path\n\n### 8. Compose file at /vmPool/r/compose/xpltd_chrysopedia/ ✅\nSymlink verified: docker-compose.yml → repo's docker-compose.yml

## Definition of Done Results

| # | Item | Met | Evidence |\n|---|------|-----|----------|\n| 1 | GitHub repo created (private) | ✅ | gh repo view returns PRIVATE |\n| 2 | Clone on ub01 up to date | ✅ | git pull succeeds, all files present |\n| 3 | docker-compose.yml at compose dir | ✅ | Symlink at /vmPool/r/compose/xpltd_chrysopedia/ |\n| 4 | All containers healthy | ✅ | docker ps shows 7/7 healthy |\n| 5 | Alembic migration applied | ✅ | alembic current returns 001_initial (head) |\n| 6 | .env with correct credentials | ✅ | Health endpoint confirms DB connected, LLM API key set |\n| 7 | CLAUDE.md redirect | ✅ | File exists with ub01 path |\n| 8 | README updated | ✅ | Deployment section with service URLs |

## Requirement Outcomes

### R010 — Docker Compose Deployment: validated → validated (reinforced)\nNow proven end-to-end on actual ub01 hardware: docker compose up -d brings up all 7 services, data persists at /vmPool/r/services/chrysopedia_*, compose config at /vmPool/r/compose/xpltd_chrysopedia/ with XPLTD naming conventions.

## Deviations

4 healthcheck fixes needed iteratively (Ollama, Qdrant, web, worker lacked expected CLI tools). Alembic env.py sys.path needed Docker-aware fix. ZFS uid mismatch required sudo chown.

## Follow-ups

Set up a cron job or systemd timer on ub01 to auto-restart chrysopedia containers on reboot. Consider adding GPU passthrough for Ollama if embedding performance needs improvement.
