# Multi-Tenant Dobby on Vultr — Design Spec

**Date:** 2026-03-26
**Goal:** Run 3 isolated Dobby instances on a single Vultr VPS, each with its own Claude Code auth, Slack bot, and public subdomain via Cloudflare Tunnel.

---

## Architecture

```
                    Cloudflare Tunnel
                          │
         ┌────────────────┼────────────────┐
         ▼                ▼                ▼
   tenant1.domain   tenant2.domain   tenant3.domain
         │                │                │
         ▼                ▼                ▼
    localhost:8420   localhost:8421   localhost:8422
         │                │                │
    ┌────┴────┐      ┌────┴────┐      ┌────┴────┐
    │ Container│      │ Container│      │ Container│
    │ tenant1  │      │ tenant2  │      │ tenant3  │
    │          │      │          │      │          │
    │ dobby    │      │ dobby    │      │ dobby    │
    │ slack    │      │ slack    │      │ slack    │
    │ claude   │      │ claude   │      │ claude   │
    └─────────┘      └─────────┘      └─────────┘
         │                │                │
    tenant1-data     tenant2-data     tenant3-data
    (volume)         (volume)         (volume)
```

## Per-Tenant Isolation

| Resource | How Isolated |
|----------|-------------|
| Dobby daemon | Separate container, unique port (8420/8421/8422) |
| SQLite DB | Inside tenant's data volume |
| Claude Code auth | `~/.claude` persisted in tenant's data volume |
| Slack tokens | Separate `.env` file per tenant |
| Config | Separate `dobby.yaml` per tenant |
| Results | Inside tenant's data volume |
| Public URL | Unique subdomain via Cloudflare Tunnel |

## Dockerfile Changes

Extend current Dockerfile to also install:
- Node.js + npm (for Claude Code CLI)
- Claude Code CLI (`@anthropic-ai/claude-code`)

New entrypoint script that:
1. Starts `dobby start --config /data/dobby.yaml` in background
2. Waits for health check to pass
3. If Slack tokens are set, starts `dobby slack --dobby-url http://127.0.0.1:${DOBBY_PORT}` in background
4. Tails logs / keeps container alive

## docker-compose.multi.yml

3 service blocks (tenant1, tenant2, tenant3) sharing a single build image. Each service has:
- Unique port mapping
- Own named volume for `/data`
- Own `.env` file
- Restart policy: `unless-stopped`

## Per-Tenant Config

### Config Template (tenantN.yaml)
```yaml
host: "0.0.0.0"
port: <8420 + N-1>
db_path: "/data/dobby.db"
results_dir: "/data/results"
hub_url: "<subdomain URL - set later>"
max_concurrent_agents: 3
default_timeout: 3600
```

### Env Template (tenantN.env)
```bash
DOBBY_PORT=<port>
SLACK_BOT_TOKEN=
SLACK_APP_TOKEN=
ANTHROPIC_API_KEY=
SLACK_CHANNEL=
```

## Cloudflare Tunnel

A `cloudflared` config template mapping subdomains to localhost ports. Runs on the host (not in containers). Users fill in their domain later.

## Build Location

All Docker builds and data stored in `/mnt/data/dobby/` on the VPS (only disk with sufficient space).

## Code Changes

- `deploy/multi-tenant/Dockerfile` — extended Dockerfile with Claude Code
- `deploy/multi-tenant/docker-compose.yml` — 3 tenant services
- `deploy/multi-tenant/entrypoint.sh` — starts dobby + slack
- `deploy/multi-tenant/tenant1.yaml`, `tenant2.yaml`, `tenant3.yaml` — config per tenant
- `deploy/multi-tenant/tenant1.env.example`, `tenant2.env.example`, `tenant3.env.example` — env templates
- `deploy/multi-tenant/cloudflared-config.yml.example` — tunnel routing template
- `deploy/multi-tenant/README.md` — setup instructions

No changes to core dobby code needed.
