Docker systems design — applying multi-tenant hosting to our production node
Docker systems design — applying multi-tenant hosting to our production node
Young, 2026-10-13: "Min study with Tam for Docker systems design. How to apply to our current production server."
Tam's A2A is wedged (confirmed), so this design is vault-routed. Everything below was measured live on 161.33.71.67 on 2026-10-13 — nothing assumed.
1. The node, measured
| Spec | Measured | Young's brief |
|---|---|---|
| Arch | aarch64 (Neoverse-N1) | ARM ✅ |
| CPU | 4 | 4 vCPU ✅ |
| RAM | 23 GiB total, 21 GiB available | 24 GB ✅ (close) |
| Disk | 193 GB, 179 GB free (14 G used) | 200 GB ✅ |
| Docker | 29.7.2, Compose v5.4.0, active | — (already installed) |
| Containers running | none — clean slate | — |
Docker is already there. That removes the first deployment risk entirely.
Current residents (must not break)
| Service | Port | Notes |
|---|---|---|
nginx | 80, 443 | reverse proxy + TLS termination |
solos-docs.service | 127.0.0.1:8091 | docs.youngle.au |
pulse-site.service | 127.0.0.1:8092 | pulse.youngle.au |
sol.service (node) | 127.0.0.1:4090 | sol.youngle.au |
| yukonit site | 127.0.0.1:8012 | yukonit.youngle.au |
tailscaled | — | tailnet: solos, lab (offline 13d), pulse |
Resident footprint: ~1.5 GiB RAM, ~14 GB disk. Available for tenants: ~20 GiB RAM, ~179 GB disk.
🔴 Finding: Tailscale is already a mesh
100.124.146.127 solos (this node)
100.94.188.127 lab offline, last seen 13d ago
100.85.164.30 pulse active, direct
This changes the A2A answer. The earlier problem — LAB's :9900 bound to
127.0.0.1, unreachable from pulse — has a cleaner fix than binding to the LAN:
bind to the Tailscale interface (100.x) instead of 0.0.0.0. Same isolation
benefit (only tailnet members reach it), no LAN exposure, no firewall rule needed.
Sol's card should carry this option.
2. Tenancy design — the core decision
Per-tenant Docker Compose project, on a per-tenant bridge network, with NO host port publication. nginx proxies to container IPs on the tenant network.
Why this beats host-port allocation:
| Approach | Collision risk | Isolation | Complexity |
|---|---|---|---|
| Host ports per tenant (9001, 9002…) | 🔴 high — must track a global map | weak | low |
| Per-tenant network + container-name proxy | ✅ none — each network has its own IP space | strong | medium |
| Single shared cluster | 🔴 tenant blast radius | none | high |
The design:
nginx (host, 80/443)
│ proxy_pass http://medusa-<tenant>:9000 (container DNS on the tenant network)
▼
docker network: tenant-<slug> (bridge, isolated)
├── postgres:15-alpine volume: pg-<slug>
├── redis:7-alpine volume: redis-<slug>
├── medusa-server WORKER_MODE=server, DISABLE_MEDUSA_ADMIN=false
├── medusa-worker WORKER_MODE=worker, DISABLE_MEDUSA_ADMIN=true
├── storefront Next.js
└── eve-agent Nitro (self-hosted)
nginx connects to tenant networks (docker network connect) and proxies by
container name — so no tenant publishes a host port at all. Port collisions become
structurally impossible, not a checklist item.
Resource ceilings (prevent one tenant starving the node)
Every container gets explicit limits — the node has 4 CPUs shared with four existing services, and one runaway tenant must not take the others down:
deploy:
resources:
limits: { cpus: "1.0", memory: 2G } # medusa-server
# worker 0.5/1G · storefront 0.5/1G · pg 0.5/0.7G · redis 0.25/0.1G · eve 0.25/0.3G
Per-tenant ceiling ≈ 3.0 CPU / 5.1 GiB — 4 tenants fit the measured budget with headroom. This matches Tam's 4.8 GB/stack estimate.
3. The four constraints that shape everything
3.1 Builds are the real CPU risk, not runtime
medusa build and Next.js builds spike hard on 4 shared vCPUs. Tam's study already
named the fix — build once per template release, one shared image for all tenants.
Concretely: a CI/build step produces solos-medusa:<tag>, pushed to a local registry
container on the node; tenants pull it. Never build per-tenant.
3.2 Disk is the long-term ceiling, not RAM
179 GB free, but each tenant carries a Postgres volume + backups + logs. Budget: ~2 GB image layers (shared) + ~1 GB/tenant volumes + backups. Backups go offsite (not on this node) — the node cannot be both the host and the backup target.
3.3 Existing services must not be disturbed
nginx config is shared. Tenant vhosts are added as new files in sites-enabled/,
never by editing the existing four. TLS via the existing letsencrypt pattern. A tenant
onboarding that touches docs.youngle.au or pulse.youngle.au is a defect.
3.4 Tailscale is the private plane
Tenant admin panels should not be publicly exposed by default. Bind admin to the tailnet interface, public storefront only through nginx. This is free security we already own.
4. Onboarding — one script, probe-gated
Per Tam's study, and matching our probe discipline:
1. slug=<tenant> # validate: lowercase, no collisions
2. generate secrets # openssl rand -hex 32 × 4 (COOKIE, JWT, DB, REDIS)
# → ~/.solos-tenants/<slug>/secrets.env, chmod 0600
3. render compose # template → docker-compose.<slug>.yml (per-tenant names)
4. docker network create tenant-<slug>
5. docker compose -f ... up -d
6. run predeploy # medusa db:migrate + link sync
7. create admin user # npx medusa user -e … -p … (generated, stored 0600)
8. nginx vhost + certbot
9. GATE: curl /health → OK AND /eve/v1/health → OK AND admin loads
10. only then: mark tenant live
Nothing is "live" until step 9 passes. Same rule as our Heart worker: a capability with no probe is not a deliverable.
Updates
git tag → pull template → predeploy (auto-migrate) → restart → health re-check
Rollback = prior tag + prior volumes (never deleted on update)
5. What this means for Sol's lane (feature specs)
The Docker design constrains what Sol can spec:
- Every new feature must be per-tenant configurable — no global state. A feature that writes to a shared table breaks the isolation model.
- Admin features bind to tailnet, not public — any admin surface he specs needs a network-scope decision.
- POS payments — if it needs a terminal service, it runs as another container on the tenant network (no host port).
- Resource limits are part of the spec — a feature that needs >1 CPU steady breaks the 4-tenant budget.
6. What this means for Mira's lane (finance)
The design gives her the real numbers:
- COGS per tenant ≈ 0 marginal on the free tier, but the node caps at ~4 tenants
- 5th tenant = new node (paid VPS or second ARM) — that's the step-function in the cost model
- Disk/backup is the long-term ceiling, so backup storage is a real line item
- The tailnet and nginx are already paid for — shared infra, no per-tenant cost
7. Honest gaps in this design
- Not built. This is a design on measured facts, not a running tenant.
- No tenant has been deployed — the onboarding script is a spec, not a tested script.
- Backup/restore not designed beyond "goes offsite" — needs a real restore drill (a backup never restored is not a backup).
- The local registry container is proposed, not built.
- EVE health endpoint (
/eve/v1/health) is from Tam's study, not probed by me. - Tailscale A2A fix is a recommendation, not implemented — Sol's lane.
Business plan v2 — one package, AUD, honest infra cost
Young's three rulings, 2026-10-14: Oracle ARM shape confirmed as ours (4vCPU/24GB/200GB) · POS gaps + EVE beta are build work (Sol specs, Min+Tam Docker design) · prices in AUD.
EVE customer service on Medusa — design of record
Young, 2026-10-19: "Drop AgenticMail, use as reference for features and function. Work together via A2A and work on the same file instead of individual, combined the power of 2 brains. Focus more on eve-customer-service and Medusa integrate notification function. True we need to train EVE about security and validation of order, customer … and more before serving the customer. Email still carries receipts and shipping and O365 is still a product line you sell. EVE structure and foundation are very strong framework — we can optimise it to create product such as Marketing, Social Media Management onto Medusa."

