- deploy/booking: shared Easy!Appointments stack with brand-matched wizard (flatpickr recolor, single-tenant provider hide, iframe auto-fit height reporter) - deploy/clients: per-client isolated nginx compose stacks with _template scaffold, new-client.sh, and happynails live site - deploy/backup: smb-db backup script - n8n: booking-sync workflow; onboarding tweaks - playbooks: lead-to-customer lifecycle + outreach - templates: nail-studio landing previews - backoffice: app/db/init/compose updates Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clients/ — one isolated stack per business client
Each signed client gets their own Compose project (smb-client-<slug>) running a
single lightweight nginx:alpine container that serves their static site on their
domain. Clients are fully separated: independent lifecycle, logs, and removal; no shared
container or state. Booking and lead data still flow into the shared CRM (Postgres
source of truth) via the n8n webhooks baked into each client's page.
Why a container per client (not one shared host)
- Isolation — update / restart / remove one client without touching any other.
- Clean billing & offboarding —
docker compose downremoves exactly that client. - Cheap — static nginx idles at a few MB; capped at 64 MB each, so dozens fit the 16 GB box.
Layout
deploy/clients/
├── _template/ # DON'T deploy — the source template
│ ├── docker-compose.yml (name: smb-client-${CLIENT_SLUG})
│ ├── .env.example
│ └── site/index.html
├── new-client.sh # scaffolds a client folder from _template/
└── <slug>/ # one folder per real client (created by the script)
├── docker-compose.yml
├── .env (CLIENT_SLUG, CLIENT_DOMAIN)
└── site/ (the client's branded page)
Add a client
cd deploy/clients
./new-client.sh happynails happynails.de ../../templates/landing/preview-happynails
Then follow the printed steps: add the Caddy block + reload, point DNS, and docker compose up -d
on the homelab. Each client lives at /home/mivanchenko/clients/<slug>/ on the box.
How this fits the whole system (compose groups)
edge/ caddy → TLS, routing, basic-auth (one, shared)
automation/ n8n (+ postgres, redis) → n8n.mivanchenko.de (one, shared)
crm/ smb-db + smb-crm → onboard.mivanchenko.de/crm (one, shared)
demos/ smb-demos → demos.mivanchenko.de (prospect previews)
clients/ smb-client-<slug> × N → each client's domain (one per client) ← this dir
Shared infra (edge / automation / crm) stays single; only the client-facing sites multiply,
one isolated stack each. Previews for prospects stay in demos/ until they sign — then
scaffold them a real isolated stack here.