diff --git a/.gitignore b/.gitignore index 0fb93c4..e2eacd0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ venv/ readme.md- *.bak hardware_state.json +.env +secrets/ +searxng/ diff --git a/docker.md b/docker.md new file mode 100644 index 0000000..d6f83c7 --- /dev/null +++ b/docker.md @@ -0,0 +1,681 @@ +# Docker Distribution — Architecture & Planning + +> **Part of B3 (v1.0 gate).** This document catalogs every service, volume, port, configuration, and decision needed to ship jarvisChat as a `docker compose` stack. It also defines extraction (setup) and back-out (uninstall) procedures so nothing is lost when reality disagrees with the plan. + +## 1. Stack Overview + +``` +┌─────────────────────────────────────────────────────────┐ +│ docker compose stack │ +│ │ +│ ┌────────────┐ ┌──────────┐ ┌────────────────────┐ │ +│ │ SearXNG │ │ Qdrant │ │ RabbitMQ │ │ +│ │ :8888 │ │ :6333 │ │ :5672 / :15672 │ │ +│ └──────┬──────┘ └────┬─────┘ └────────┬───────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ jarvisChat (FastAPI) │ │ +│ │ :8080 (HTTP) │ │ +│ │ │ │ +│ │ SQLite ◄── jarvischat.db (volume) │ │ +│ │ Uploads ◄── /app/uploads (volume) │ │ +│ └──────────┬──────────────┬───────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ llama-server │ │ Ollama │ │ +│ │ :8081 │ │ :11434 │ │ +│ │ (GPU/RPC) │ │ (embeddings) │ │ +│ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### Service roles + +| Service | Image | Role | +|---------|-------|------| +| **jarvisChat** | Custom `Dockerfile` | FastAPI app serving UI + API | +| **SearXNG** | `searxng/searxng:latest` | Privacy-respecting web search | +| **Qdrant** | `qdrant/qdrant:latest` | Vector database for RAG | +| **RabbitMQ** | `rabbitmq:4-management` | Message broker for AMQP cluster | +| **llama-server** | `ghcr.io/ggml-org/llama.cpp:server` | LLM inference (OpenAI-compat API) | +| **Ollama** | `ollama/ollama:latest` | Embeddings for RAG chunk vectors | + +### Non-containerized (host-level) + +| Component | Reason | +|-----------|--------| +| AMD GPU driver + ROCm | Kernel access required for GPU compute | +| llama.cpp RPC workers | Runs on *other* hosts — not on the Docker host | +| `rocm-smi` | Hardware stats — not needed for core function | +| `psutil` | Already inside the container via pip | + +--- + +## 2. Service Catalog + +### 2.1 jarvisChat (FastAPI app) + +**Image:** `jarvischat:latest` (built from `Dockerfile`) + +**Ports:** +| Container | Host | Purpose | +|-----------|------|---------| +| 8080 | 8080 | HTTP API + UI | + +**Volumes:** +| Container path | Type | Purpose | +|----------------|------|---------| +| `/app/jarvischat.db` | named volume `jarvischat_data` | SQLite database | +| `/app/uploads` | named volume `jarvischat_uploads` | Uploaded files | +| `/app/hardware_state.json` | (inside volume) | Cached hardware probe | + +**Dependencies:** Wait for SearXNG, Qdrant, RabbitMQ, llama-server, Ollama before serving. + +**Restart:** `unless-stopped` + +**Healthcheck:** `curl -f http://localhost:8080/` + +### 2.2 SearXNG + +**Image:** `searxng/searxng:latest` + +**Ports:** +| Container | Host | Purpose | +|-----------|------|---------| +| 8080 | 8888 | Search API | + +**Volumes:** +| Container path | Type | Purpose | +|----------------|------|---------| +| `/etc/searxng` | named volume `searxng_config` | `settings.yml` | + +**Environment:** +```env +SEARXNG_BASE_URL=https://localhost:8888 +``` + +**Config override (`/etc/searxng/settings.yml`):** +```yaml +search: + safe_search: 0 + autocomplete: "" +server: + secret_key: ${SEARXNG_SECRET_KEY} + limiter: false + image_proxy: false + method: GET + port: 8080 + bind_address: "0.0.0.0" +``` + +**Restart:** `unless-stopped` + +### 2.3 Qdrant + +**Image:** `qdrant/qdrant:latest` + +**Ports:** +| Container | Host | Purpose | +|-----------|------|---------| +| 6333 | 6333 | HTTP API | +| 6334 | — | gRPC (internal only) | + +**Volumes:** +| Container path | Type | Purpose | +|----------------|------|---------| +| `/qdrant/storage` | named volume `qdrant_storage` | Vector index data | + +**Environment:** +```env +QDRANT__SERVICE__GRPC_PORT=6334 +``` + +**Restart:** `unless-stopped` + +### 2.4 RabbitMQ + +**Image:** `rabbitmq:4-management` + +**Ports:** +| Container | Host | Purpose | +|-----------|------|---------| +| 5672 | 5672 | AMQP messaging | +| 15672 | — | Management UI (internal only) | + +**Volumes:** +| Container path | Type | Purpose | +|----------------|------|---------| +| `/var/lib/rabbitmq` | named volume `rabbitmq_data` | Message store | + +**Environment:** +```env +RABBITMQ_DEFAULT_USER=jarvischat +RABBITMQ_DEFAULT_PASS_FILE=/run/secrets/rabbitmq_password +RABBITMQ_DEFAULT_VHOST=/ +``` + +**Restart:** `unless-stopped` + +### 2.5 llama-server + +**Image:** `ghcr.io/ggml-org/llama.cpp:server` + +**Ports:** +| Container | Host | Purpose | +|-----------|------|---------| +| 8081 | 8081 | OpenAI-compat API | + +**Volumes:** +| Container path | Type | Purpose | +|----------------|------|---------| +| `/models` | bind mount `./models` | Model GGUF files | + +**Environment:** +```env +LLAMA_ARG_MODEL=/models/ +LLAMA_ARG_N_GPU_LAYERS=0 # set >0 for GPU offload +LLAMA_ARG_MAIN_GPU=0 +LLAMA_ARG_CTX_SIZE=4096 +LLAMA_ARG_HOST=0.0.0.0 +LLAMA_ARG_PORT=8081 +LLAMA_ARG_EMBEDDINGS=1 +LLAMA_ARG_LOGPROBS=1 +LLAMA_ARG_RPC= # optional: comma-separated RPC endpoints +``` + +**Restart:** `unless-stopped` + +**Healthcheck:** `curl -f http://localhost:8081/health` + +**Notes:** +- Models directory bind mount — user places `.gguf` files in `./models/` on the host +- RPC offload to other machines (e.g., `10.0.0.50:50052,10.0.0.51:50052`) +- If no GPU, set `LLAMA_ARG_N_GPU_LAYERS=0` for CPU-only +- `LLAMA_ARG_EMBEDDINGS=1` required for perplexity scoring +- `LLAMA_ARG_LOGPROBS=1` required for auto-search trigger + +### 2.6 Ollama + +**Image:** `ollama/ollama:latest` + +**Ports:** +| Container | Host | Purpose | +|-----------|------|---------| +| 11434 | 11434 | Embeddings API | + +**Volumes:** +| Container path | Type | Purpose | +|----------------|------|---------| +| `/root/.ollama` | named volume `ollama_models` | Pulled model blobs | + +**Restart:** `unless-stopped` + +**Notes:** +- Used exclusively for embeddings (`/api/embeddings`), not inference +- Typically needs a small model like `all-minilm:latest` or `nomic-embed-text:latest` +- Consider replacing Ollama with llama-server's built-in embedding if it supports the same model — would remove one container + +--- + +## 3. Configuration Management + +### 3.1 `.env` file (generated by setup wizard) + +```env +# --- Secrets (auto-generated, change before production) --- +JARVISCHAT_ADMIN_PIN= +JARVISCHAT_COMPLETIONS_API_KEY= +JARVISCHAT_ALLOW_DEFAULT_PIN=false +RABBITMQ_PASSWORD= +SEARXNG_SECRET_KEY= + +# --- Host discovery (auto-detected by setup wizard) --- +LLAMA_SERVER_BASE=http://llama-server:8081 +OLLAMA_BASE=http://ollama:11434 +SEARXNG_BASE=http://searxng:8888 +QDRANT_URL=http://qdrant:6333 +RABBITMQ_HOST=rabbitmq +RABBITMQ_PORT=5672 + +# --- Performance tuning (calculated by setup wizard) --- +RAG_MAX_VECTORS=50000 +RAG_EVICTION_HIGH_WATER=0.80 +RAG_EVICTION_LOW_WATER=0.20 +RAG_EVICTION_BATCH=1000 + +# --- llama-server options --- +LLAMA_MODEL=llama3.1-8b-instruct.Q4_K_M.gguf +LLAMA_N_GPU_LAYERS=0 +LLAMA_RPC_ENDPOINTS= +LLAMA_CTX_SIZE=4096 + +# --- Ollama --- +OLLAMA_EMBED_MODEL=all-minilm:latest + +# --- Network --- +JARVISCHAT_ALLOWED_CIDRS=127.0.0.0/8,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 +JARVISCHAT_TRUSTED_ORIGINS= +JARVISCHAT_TRUST_X_FORWARDED_FOR=false +``` + +### 3.2 Mapping of config.py → .env variable + +Every config.py default that references an external service must accept a matching env var at runtime: + +| config.py constant | .env variable | Service | +|-------------------|---------------|---------| +| `LLAMA_SERVER_BASE` | `LLAMA_SERVER_BASE` | llama-server | +| `OLLAMA_BASE` | `OLLAMA_BASE` | Ollama | +| `SEARXNG_BASE` | `SEARXNG_BASE` | SearXNG | +| `QDRANT_URL` | `QDRANT_URL` | Qdrant | +| `COMPLETIONS_API_KEY` | `JARVISCHAT_COMPLETIONS_API_KEY` | — | +| `ALLOWED_CIDRS_RAW` | `JARVISCHAT_ALLOWED_CIDRS` | — | +| `TRUST_X_FORWARDED_FOR` | `JARVISCHAT_TRUST_X_FORWARDED_FOR` | — | +| `TRUSTED_ORIGINS` | `JARVISCHAT_TRUSTED_ORIGINS` | — | +| `RAG_MAX_VECTORS` | `RAG_MAX_VECTORS` | — (calc'd from RAM) | + +### 3.3 Secrets management + +| Secret | Generated by | Stored in | Mounted to | +|--------|-------------|-----------|------------| +| `JARVISCHAT_ADMIN_PIN` | User prompt | `.env` | jarvisChat container | +| `JARVISCHAT_COMPLETIONS_API_KEY` | Auto-generated, shown to user | `.env` | jarvisChat container | +| `RABBITMQ_PASSWORD` | Auto-generated | `.env` + Docker secret | RabbitMQ container | +| `SEARXNG_SECRET_KEY` | Auto-generated | `.env` | SearXNG container | + +**Docker secrets approach:** Use `secrets:` in compose file for RabbitMQ password (mounted as file) rather than passing via env var, since `settings.yml` in SearXNG and RabbitMQ config can reference file-based secrets without env-var leakage. + +### 3.4 Dockerfile for jarvisChat + +```dockerfile +FROM python:3.13-slim-bookworm AS builder +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +FROM python:3.13-slim-bookworm +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl && \ + rm -rf /var/lib/apt/lists/* +COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages +COPY --from=builder /usr/local/bin /usr/local/bin +COPY . . + +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ + CMD curl -f http://localhost:8080/ || exit 1 + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"] +``` + +**Multi-stage rationale:** First stage compiles/bundles packages (wheels), final stage is minimal. Devs can skip builder with `--target builder` for live-reload with volume mount. + +--- + +## 4. docker-compose.yml structure + +```yaml +services: + jarvischat: + build: . + ports: ["8080:8080"] + volumes: + - jarvischat_data:/app/jarvischat.db + - jarvischat_uploads:/app/uploads + env_file: .env + depends_on: + searxng: { condition: service_started } + qdrant: { condition: service_started } + rabbitmq: { condition: service_healthy } + llama-server: { condition: service_healthy } + ollama: { condition: service_started } + restart: unless-stopped + + searxng: + image: searxng/searxng:latest + ports: ["8888:8080"] + volumes: + - ./searxng/settings.yml:/etc/searxng/settings.yml:ro + - searxng_config:/etc/searxng + env_file: .env + restart: unless-stopped + + qdrant: + image: qdrant/qdrant:latest + ports: ["6333:6333"] + volumes: + - qdrant_storage:/qdrant/storage + restart: unless-stopped + + rabbitmq: + image: rabbitmq:4-management + ports: ["5672:5672"] + volumes: + - rabbitmq_data:/var/lib/rabbitmq + env_file: .env + secrets: + - rabbitmq_password + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"] + interval: 15s + timeout: 5s + retries: 3 + restart: unless-stopped + + llama-server: + image: ghcr.io/ggml-org/llama.cpp:server + ports: ["8081:8081"] + volumes: + - ./models:/models:ro + env_file: .env + command: > + --model /models/${LLAMA_MODEL} + --host 0.0.0.0 --port 8081 + --ctx-size ${LLAMA_CTX_SIZE:-4096} + --n-gpu-layers ${LLAMA_N_GPU_LAYERS:-0} + --embeddings + --logprobs + ${LLAMA_RPC_ENDPOINTS:+--rpc ${LLAMA_RPC_ENDPOINTS}} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/health"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + restart: unless-stopped + + ollama: + image: ollama/ollama:latest + ports: ["11434:11434"] + volumes: + - ollama_models:/root/.ollama + healthcheck: + test: ["CMD", "ollama", "list"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + restart: unless-stopped + +volumes: + jarvischat_data: + jarvischat_uploads: + searxng_config: + qdrant_storage: + rabbitmq_data: + ollama_models: + +secrets: + rabbitmq_password: + file: ./secrets/rabbitmq_password.txt +``` + +**Notes:** +- GPU reservations use `resources.reservations.devices` — this is compose v3.8+. For AMD GPUs, replace `driver: nvidia` with `driver: amd` (experimental Docker support). For hosts without GPU, omit the `deploy` block entirely. +- The `deploy` block only applies when deployed as a swarm stack. For `docker compose`, GPU access may need `--gpus all` or `device_requests` in config. Verify compatibility. +- SearXNG config file (`settings.yml`) is bind-mounted read-only from the host repo clone — the setup wizard should generate this file. + +--- + +## 5. Networking + +### 5.1 Internal communication (compose network) + +| From | To | Port | Protocol | +|------|----|------|----------| +| jarvisChat | llama-server | 8081 | HTTP | +| jarvisChat | Ollama | 11434 | HTTP | +| jarvisChat | SearXNG | 8080 | HTTP | +| jarvisChat | Qdrant | 6333 | HTTP | +| jarvisChat | RabbitMQ | 5672 | AMQP | +| RabbitMQ | (cluster peers) | 4369 | EPMD | +| RabbitMQ | (cluster peers) | 25672 | Inter-node | + +### 5.2 Exposed ports (host-facing) + +| Port | Service | Should expose? | Notes | +|------|---------|---------------|-------| +| 8080 | jarvisChat | ✅ Required | UI + API | +| 8888 | SearXNG | Optional | Only if user wants standalone search | +| 6333 | Qdrant | Optional | Only for external tooling | +| 5672 | RabbitMQ | Optional | Only for remote AMQP clients | +| 15672 | RabbitMQ mgmt | ❌ Internal | Healthcheck only | +| 8081 | llama-server | Optional | Only for external tooling | +| 11434 | Ollama | Optional | Only for external tooling | + +**Design decision:** By default, only port 8080 (jarvisChat) is published. All other services remain on the internal compose network. Advanced users can opt-in by uncommenting `ports:` blocks. + +### 5.3 Reverse proxy consideration + +For production, a reverse proxy (Caddy, nginx, Traefik) should sit in front: + +```yaml +# Optional — compose profile: "proxy" +caddy: + image: caddy:latest + ports: ["80:80", "443:443"] + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data +``` + +This is out of scope for v1.0 but documented for future. + +--- + +## 6. Setup Wizard (Extraction) + +`setup.sh` — idempotent, interactive, runs on first boot. + +### Flow + +``` +1. CHECK: Is .env present? + ├── YES → skip to step 7 (or ask to regenerate) + └── NO → continue + +2. INTRO: Print banner, explain what's about to happen + +3. PROBE: Run hardware assessment + ├── psutil → RAM total, CPU count + ├── rocm-smi → VRAM (optional, best-effort) + └── nvidia-smi → VRAM (optional, best-effort) + +4. NETWORK: Ask for + ├── Hostname / LAN IP for this machine + ├── Admin PIN (4 digits, or accept auto-generated) + └── (Optional) RPC endpoints for GPU offload + +5. CALCULATE: + ├── RAG_MAX_VECTORS = max(1000, int(available_ram_gb * 100_000)) + ├── LLAMA_N_GPU_LAYERS = 0 (CPU default; offer GPU detection) + ├── LLAMA_MODEL = default gguf filename + └── RABBITMQ_PASSWORD = openssl rand -hex 20 + +6. GENERATE: + ├── .env file from template + ├── ./secrets/rabbitmq_password.txt + ├── ./searxng/settings.yml (with generated secret_key) + └── ./models/README.txt (instructions for placing .gguf) + +7. VERIFY: + ├── docker and docker compose plugin installed + ├── docker compose version >= 2.x + ├── SUCCESS → "Run: docker compose up -d" + └── FAILURE → show diagnostics and links + +8. EXTRACT model: + ├── Prompt for download URL or local path + ├── Offer to pull from HuggingFace if huggingface-cli available + └── Guides user to place file in ./models/ +``` + +### What setup.sh creates on disk + +``` +./docker-deploy/ +├── .env # All env vars (SECRET — add to .gitignore) +├── docker-compose.yml # Compose stack definition +├── Dockerfile # jarvisChat image build +├── secrets/ +│ └── rabbitmq_password.txt # RabbitMQ password file +├── searxng/ +│ └── settings.yml # SearXNG config with generated secret_key +├── models/ +│ ├── README.txt # Instructions for model placement +│ └── .gguf # (user-provided) +└── setup.log # Wizard run log +``` + +### Idempotency + +Re-running `setup.sh`: +- With `.env` present: ask "Regenerate? This will overwrite existing config." +- Without `.env`: fresh run +- Never overwrites `./models/*.gguf` files +- Never touches running containers — only modifies files on disk + +--- + +## 7. Back-out Procedure (Uninstall) + +`teardown.sh` — returns the host system to its pre-install state. + +### What gets removed + +| Item | Removal method | +|------|---------------| +| Docker containers | `docker compose down -v` | +| Docker images | `docker rmi jarvischat:latest` (ask about other images) | +| Docker volumes | `docker volume rm jarvischat_data ...` (prompt first) | +| Network `jarvischat_default` | Removed with compose | +| `.env` file | `rm .env` | +| `secrets/` directory | `rm -rf secrets/` | +| `searxng/` directory | `rm -rf searxng/` | +| `setup.log` | `rm setup.log` | +| `hardware_state.json` | `rm hardware_state.json` | + +### What is preserved (by default) + +| Item | Reason | +|------|--------| +| `./models/*.gguf` | User data — prompt for deletion | +| `jarvischat.db` (in volume) | Prompt: "Keep database snapshot?" | +| `./uploads/` (in volume) | Prompt: "Keep uploaded files?" | +| Docker Engine itself | Not installed by this project — leave it | + +### Script flow + +``` +1. CHECK: docker compose file exists? + ├── NO → warn, continue + └── YES → docker compose down -v + +2. CHECK: .env exists? + ├── NO → skip + └── YES → ask: "Remove .env?" (default no) + +3. ASK: "Remove secrets/ and searxng/ directories?" (default no) + +4. ASK: "Remove Docker images? (y/N)" (default no) + ├── Y → docker rmi jarvischat:latest + ├── Y → docker image ls | grep searxng/qdrant/rabbitmq → prompt per image + └── N → skip + +5. ASK: "Keep database volume snapshot? (Y/n)" (default yes) + ├── N → docker volume rm jarvischat_data + └── Y → leave volume (can be reattached later) + +6. ASK: "Remove model files from ./models/? (y/N)" (default no) + +7. CLEANUP generated artifacts: + ├── rm -f setup.log + ├── rm -f hardware_state.json + └── rm -f docker-compose.yml + +8. SUMMARY: + ├── "Docker stack removed" + ├── "Persistent data preserved at: " + └── "Models kept at: ./models/" +``` + +### Partial rollback + +If the setup wizard fails mid-way, a partial rollback is better than leaving detritus: + +| Failure point | Clean up | +|--------------|----------| +| After .env, before compose | `rm .env; rm -rf secrets/ searxng/` | +| After compose, before first `up` | `rm docker-compose.yml; rm -rf *` | +| After `up` but before healthcheck | `docker compose down -v; rm -rf ./*` | + +`setup.sh` should trap EXIT on failure and prompt: "Clean up partial install? [y/N]" + +--- + +## 8. Open Decisions + +| Decision | Options | Priority | +|----------|---------|----------| +| **Ollama vs llama-server embeddings** | Both work. Keep both for now — remove Ollama if llama-server handles embeddings. Reduce containers = simpler. | Medium | +| **GPU support in compose** | NVIDIA: well-supported. AMD: requires `--device=/dev/kfd --device=/dev/dri` and ROCm image. Document both. | High | +| **RabbitMQ clustering vs single node** | Single node in v1.0. Clustering docs for multi-host later. | Low | +| **SearXNG config management** | Bind-mount a generated `settings.yml`, or let container create default and post-process. Bind-mount is cleaner. | Medium | +| **Reverse proxy** | Caddy is simplest for auto-HTTPS. Out of scope for v1.0 but design for it. | Low | +| **Healthcheck strategy** | `depends_on` with `condition: service_healthy` is the safest approach but increases startup time. Acceptable. | Medium | +| **Database migration** | SQLite file in volume — no migration needed for v1.0 format. If schema changes post-v1.0, need a migration container. | Low | +| **Linux vs macOS vs Windows** | Linux-primary. macOS may work with changes (no rocm-smi). Windows via WSL2 only. | Low | +| **LLM model download** | HuggingFace CLI integration in setup.sh, or manual download. Manual is simpler. | Low | +| **Dockerfile optimization** | Pin pip hashes, use `--no-cache-dir`, consider `slim` vs `alpine`. Alpine has musl compatibility issues with psutil. Stay with slim. | Medium | + +## 9. Checklist (pre-v1.0 gate) + +- [ ] `Dockerfile` written and builds clean +- [ ] `docker-compose.yml` boots all containers +- [ ] jarvisChat container reaches all services (env vars resolve correctly) +- [ ] SearXNG settings.yml generated correctly by setup.sh +- [ ] RabbitMQ password secret mounted correctly +- [ ] GPU (NVIDIA) passes through to llama-server container +- [ ] GPU (AMD) passes through to llama-server container (or documented limitation) +- [ ] `.env.example` checked in (no real secrets) +- [ ] `setup.sh` written, idempotent, tested on clean Debian +- [ ] `teardown.sh` written, tested, doesn't delete models without confirmation +- [ ] `docker compose up -d` works without any manual steps beyond setup.sh +- [ ] `docker compose down -v` followed by `setup.sh && docker compose up -d` = fresh stack +- [ ] Healthchecks prevent serving before dependencies are ready +- [ ] v1.0 release tag created + +--- + +## 10. Files to create for B3 + +``` +docker.md ← this file (planning doc) +Dockerfile ← jarvisChat image +docker-compose.yml ← full stack +.env.example ← template without secrets +setup.sh ← extraction wizard +teardown.sh ← back-out utility +searxng/ + settings.yml ← SearXNG config (generated by setup.sh) +secrets/ + rabbitmq_password.txt ← generated by setup.sh +models/ + README.txt ← instructions for placing .gguf +```