Scalability Analysis & Architecture Roadmap
What This Document Is
Section titled “What This Document Is”This is the master plan for making Magic e-VERSE scalable. It starts from where we are today, maps out every single project and service, and for each one says exactly what happens to it. The production server is blank — everything described here is built from scratch.
Part 1: What We Have Today
Section titled “Part 1: What We Have Today”The Setup
Section titled “The Setup”- 1 home server (Wayne’s) running everything — development AND production
- ~60 Docker containers serving live client traffic
- 1 developer (Wayne) working directly on the server via Claude Code
- 1 co-founder (Michiel) who also works on the server
- 1 freelance designer
- No Git workflow — Wayne edits files directly, copies them between folders
- No dev/prod separation — same server, same machine
Full Container Inventory (60 containers)
Section titled “Full Container Inventory (60 containers)”Commerce Tenants — 41 containers:
| Tenant | Backend | Storefront | Redis | Meilisearch | Notes |
|---|---|---|---|---|---|
development | Running | Running | Running | Running | Wayne’s dev workspace |
brinxx | Running | Running | Running | Running | Live client |
default | Running | Running | Running | Running | Live client |
logohorloge | Running | Running | Running | Running | Has unique 2D designer + aplt-techniques |
bovisales | Running | Running | Running | Running | Live client |
demo | Running | Running | Running | Running | Demo environment |
desluis | Running | Running | Running | — | Live client |
jodasign | Running | Running | — | Running | Has extended quotations |
mondial | Running | Running | Running | Running | Symlink to jodasign — not a real tenant |
spranz | Running | Running | Running | Running | Live |
toolvizion | Running | Running | Running | — | Live |
Platform Services — 19 containers:
| Container | What | Notes |
|---|---|---|
magic_pim_backend_dev | PIM system | Master product database |
magic_pim_postgres_dev | PostgreSQL | ALL databases live here |
magic_pim_redis_dev | PIM Redis | |
master_magic_backend_dev | ? | Purpose unclear — needs investigation |
master_magic_redis_dev | ? | Purpose unclear |
magic_agent_redis | Agent Redis | AI assistants |
magic_agent_spranz_redis | Agent Spranz Redis | Spranz-specific agent |
magic_n8n | n8n #1 | Why 4 instances? |
magic_n8n_2 | n8n #2 | |
magic_n8n_3 | n8n #3 | |
magic_n8n_clean | n8n #4 | |
magic_terminal_sshwifty | Web SSH | |
mysql | MySQL | Portal docs, workflow data |
nextcloud_app | Nextcloud | File sharing |
nextcloud_db | Nextcloud DB | |
omada-controller | Omada | Network management |
onlyoffice_docs | OnlyOffice | Document editing |
preflight-service | Preflight | ? |
rembg-service | Background removal | Image processing |
vaultwarden | Passwords |
Full Project Inventory (40+ projects)
Section titled “Full Project Inventory (40+ projects)”| Project | What It Is | Has Git? | Has Docker? | Status |
|---|---|---|---|---|
| Commerce | ||||
magic_commerce/magic_development | Dev tenant (source of truth) | No | Yes | Active |
magic_commerce/magic_brinxx | Brinxx tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_default | Default tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_logohorloge | Logohorloge tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_bovisales | Bovisales tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_demo | Demo tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_desluis | De Sluis tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_jodasign | JoDa Sign tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_mondial | Symlink to jodasign | No | Yes | Hack |
magic_commerce/magic_spranz | Spranz tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/magic_toolvizion | ToolVizion tenant (NVMe symlink) | No | Yes | Live |
magic_commerce/master_magic | Master billing? | No | Yes | Unknown |
| Platform | ||||
magic_portal | Main portal (Vite + React) | No | No | Active |
magic_beta_portal | Beta portal | No | No | Unknown |
magic_tenant_portal | Tenant portal | No | No | Unknown |
magic_project_portal | Project portal | No | No | Unknown |
magic_management | Management tool | No | No | Unknown |
magic_agent | AI assistants + flowbuilder | Yes | Yes | Active |
magic_connector | System connectors | Yes (GitHub) | Yes | Active |
magic_docs | Starlight documentation | No | Yes | Active |
magic_dev_projects | Dev project tracking | No | No | Active |
| Design & Media Tools | ||||
magic_editor | Image/product editor | Yes (GitHub) | No | Unknown |
magic_modal | Logo designer modal (Spranz/Kie.ai) | Yes (GitHub) | Yes | Active |
magic_3d | 3D product viewer | No | No | Unknown |
magic_3d_mv | 3D multiview | No | No | Unknown |
magic_logo | Logo/designer tools | No | No | Active |
magic_logo_packages | npm packages for logo tools | No | No | Unknown |
magic_resize | Image resize service | Yes (GitHub) | No | Unknown |
magic_moodshot | Product moodshot generator | No | No | Unknown |
| Infrastructure | ||||
magic_terminal | Web SSH (sshwifty) | Yes (GitHub) | Yes | Active |
magic_contact | Contact system | Yes (GitHub) | No | Unknown |
magic_messageserver | Messaging | No | No | Unknown |
mailer | Email service | No | No | Unknown |
| Other / Unknown | ||||
magiceverse_evolved | New Next.js app | No | No | Unknown |
maw_kalender | Calendar app | No | No | Unknown |
maw_landing | Landing page | No | No | Unknown |
mawordering | MAW ordering | No | No | Unknown |
simscan | SIM scanner | No | No | Unknown |
wireframes | Design wireframes | No | No | Static |
magic_infrastructure_map | Infra docs | No | No | Static |
magic_manager | Manager tool | No | No | Unknown |
Backend Code: How Different Are Tenants Really?
Section titled “Backend Code: How Different Are Tenants Really?”We diffed every tenant backend against magic_development. Result: 95%+ identical.
admin/app.tsx— admin customizationadmin/routes/01-quotations/page.tsx— sync lagadmin/routes/02-aplt-orders/page.tsx— sync lagadmin/vite.config.ts— build configapi/admin/aplt/invoices/pdf/route.ts— PDF brandingapi/admin/aplt/migrations/route.ts— migration stateapi/admin/brand-upload/route.ts— brand configapi/admin/connectors/medusa-sync/route.ts— sync configapi/admin/dev-projects/route.ts— dev toolmodules/connectors/database.ts— DB credentialsutils/password-sync.ts— DB credentials
All common differences plus tenant-unique code:
api/aplt-techniques/— Unique route for technique pricingapi/middlewares.ts—AUTHENTICATE=falsefor 2D designerapi/product-svg/[filename]/route.ts— SVG handlingapi/store/aplt/products/[sku]/techniques/route.ts— public technique data- Multiple PDF/email routes with branding differences
Mostly PDF and email template differences:
credit-notes/pdf/route.ts,invoices/eml/route.ts,invoices/pdf/route.tsorders/eml/route.ts,orders/pdf/route.tsquotations/eml/route.ts,quotations/pdf/route.tsquotations/route.ts— extended quotation featuresparameters/settings/route.ts
Every difference falls into solvable categories:
| Category | Solution |
|---|---|
| Sync lag (development is ahead) | One codebase eliminates this |
| PDF/email branding per tenant | Read from aplt_brands / aplt_cms_settings tables |
| Auth config (logohorloge AUTHENTICATE=false) | Environment variable |
| DB credentials hardcoded | Environment variables |
| Tenant-specific features (2D designer, extended quotations) | Feature flags in tenant config |
Multi-Tenancy: Why Not?
Section titled “Multi-Tenancy: Why Not?”Medusa 2.0 is single-tenant by design. Community multi-tenant solutions exist but are immature:
- Rigby RLS approach — PostgreSQL Row Level Security. Requires monkey-patching Medusa internals. Breaks on upgrades.
- eventHorizon28 link tables — Early stage, zero adoption.
- No official plugin — Feature request open, not on Medusa’s roadmap.
Decision: Stay fully isolated. Separate databases, separate containers. Solve the operational pain with one codebase, not with risky framework patches.
Part 2: What Changes — Every Project
Section titled “Part 2: What Changes — Every Project”Commerce Backends (12 copies → 1 codebase)
Section titled “Commerce Backends (12 copies → 1 codebase)”| Current | Action | Result |
|---|---|---|
magic_development/backend | Becomes the single magic-backend repo | Source of truth |
magic_brinxx/backend | Deleted — replaced by magic-backend + TENANT=brinxx env var | Deleted |
magic_default/backend | Same | Deleted |
magic_logohorloge/backend | Features merged into magic-backend behind flags | Deleted |
magic_jodasign/backend | Features merged into magic-backend behind flags | Deleted |
| All other tenant backends | Deployed from magic-backend with env var | Deleted |
magic_mondial/backend | Symlink removed — becomes a real tenant deployment | Fixed |
master_magic/backend | Investigate purpose first — then merge or kill | Decide |
Commerce Storefronts (12 → template + forks)
Section titled “Commerce Storefronts (12 → template + forks)”| Current | Action | Result |
|---|---|---|
magic_development/storefront | Becomes magic-storefront-base template | Template |
| Each client storefront | Gets its own repo, forked from base | Own repo |
Storefronts are genuinely different per client (unique designs). They stay separate.
Portals (5 → 1)
Section titled “Portals (5 → 1)”| Current | Action | Result |
|---|---|---|
magic_portal | Keep — this is the main one | Lives on |
magic_beta_portal | Merge into main portal or delete | Merged/deleted |
magic_tenant_portal | Merge into main portal or delete | Merged/deleted |
magic_project_portal | Merge into main portal or delete | Merged/deleted |
magic_management | Merge into main portal or delete | Merged/deleted |
Platform Services
Section titled “Platform Services”| Current | Action | New Repo |
|---|---|---|
magic_pim | Keep, already has GitHub repo | Magic-PIM (exists) |
magic_agent | Keep, push to GitHub | New repo |
magic_connector | Keep, already has GitHub repo | Magic-Connector (exists) |
magic_docs | Keep, push to GitHub | New repo |
Design & Media Tools
Section titled “Design & Media Tools”| Current | Action | Result |
|---|---|---|
magic_modal | Keep, has GitHub repo | Lives on |
magic_editor | Keep if active, has GitHub repo | Keep or archive |
magic_3d / magic_3d_mv | If active push to GitHub, if dead archive | Decide |
magic_logo | Keep, push to GitHub | Keep |
magic_resize | Keep, has GitHub repo | Keep |
magic_moodshot | If active keep, if dead archive | Decide |
rembg-service | Keep | Keep |
Redis & Meilisearch (24 instances → 2)
Section titled “Redis & Meilisearch (24 instances → 2)”| Current | Action | Result |
|---|---|---|
| 12× Redis instances | 1 shared Redis with key prefix per tenant | 11 removed |
| 10× Meilisearch instances | 1 shared Meilisearch with tenant indices | 9 removed |
n8n (4 → 1)
Section titled “n8n (4 → 1)”Investigate why there are 4. Keep 1, kill the rest.
Backup Files (hundreds → 0)
Section titled “Backup Files (hundreds → 0)”Delete all .bak_*, .pre_merge_backup, .current_backup, .pre_restore files once Git is in place.
Part 3: Every Deployable Service — The Complete Map
Section titled “Part 3: Every Deployable Service — The Complete Map”Before talking about servers or pipelines, here’s EVERY service that needs to run in production, where its code comes from, how Wayne works on it, and what domain it serves.
The Monorepo Decision
Section titled “The Monorepo Decision”Wayne currently works in one big directory (/mnt/data/magic_omniverse/). If we split everything into 20 separate Git repos, his workflow breaks — he’d need to know which repo to open Claude Code in, navigate between them, make separate commits. That’s a non-starter for someone who doesn’t use Git.
Decision: One monorepo for all backend services and tools. Separate repos only for storefronts (because each client has a unique design).
Wayne opens Claude Code in one directory. He can work on the backend, the portal, the agent, PIM — whatever. One commit, one push. GitHub Actions figures out what changed and only builds the affected images.
Monorepo Structure
Section titled “Monorepo Structure”magic-everse/ # ONE repo — Wayne works here├── services/│ ├── commerce-backend/ # Medusa backend (deployed 12× with diff env vars)│ │ ├── src/│ │ ├── tenants/│ │ │ ├── brinxx.json│ │ │ ├── logohorloge.json # features: ["2d-designer"]│ │ │ └── jodasign.json # features: ["extended-quotations"]│ │ └── Dockerfile│ ││ ├── pim/ # Product Information Management│ │ ├── backend/│ │ ├── storefront/│ │ └── Dockerfile│ ││ ├── portal/ # Main portal (consolidated from 4+ portals)│ │ ├── src/│ │ └── Dockerfile│ ││ ├── agent/ # AI customer assistants│ │ ├── agents/ # Agent configs (brinxx, jodasign, spranz, etc.)│ │ ├── agents-portal/ # Agent admin UI│ │ ├── escalation-server/│ │ ├── flowbuilder/│ │ └── Dockerfile│ ││ ├── connector/ # System connectors between services│ │ ├── src/│ │ └── Dockerfile│ ││ ├── logo-designer/ # Logo tools (spranz designer, logohorloge)│ │ ├── spranz/│ │ │ ├── backend/ # Spranz designer backend│ │ │ └── frontend/ # Spranz designer frontend│ │ ├── logohorloge/│ │ │ ├── backend/│ │ │ └── frontend/│ │ └── Dockerfile│ ││ ├── modal/ # Logo designer modal widget (Kie.ai)│ │ ├── src/│ │ └── Dockerfile│ ││ ├── 3d-viewer/ # 3D product visualization│ │ ├── spranz-3d/│ │ ├── default-3d/│ │ └── Dockerfile│ ││ ├── editor/ # Image/product editor│ │ ├── src/│ │ └── Dockerfile│ ││ ├── resize/ # Image resize service│ │ ├── src/│ │ └── Dockerfile│ ││ ├── moodshot/ # Product moodshot generator│ │ ├── src/│ │ └── Dockerfile│ ││ ├── rembg/ # Background removal service│ │ └── Dockerfile│ ││ ├── mailer/ # Email service│ │ ├── src/│ │ └── Dockerfile│ ││ ├── docs/ # Starlight documentation (this site)│ │ ├── src/│ │ └── Dockerfile│ ││ └── n8n/ # n8n automation (config only — uses official image)│ └── config/│├── infra/ # Production server config│ ├── docker-compose.traefik.yml│ ├── docker-compose.infra.yml # postgres, redis, meilisearch│ ├── docker-compose.tenants.yml # all tenant backends│ ├── docker-compose.storefronts.yml # all storefronts│ ├── docker-compose.platform.yml # pim, portal, agent, etc.│ ├── docker-compose.tools.yml # editor, resize, 3d, logo, etc.│ ├── docker-compose.monitoring.yml # uptime-kuma, watchtower│ ├── .env.example│ └── backup.sh│└── .github/ └── workflows/ └── build.yml # Builds ONLY what changedEvery Service — Complete Map
Section titled “Every Service — Complete Map”Here’s every service that runs in production, its source in the monorepo, its Docker image, its domain, and what triggers a rebuild:
Tenant Backends — 12 containers, 1 Docker image
| Service | Image | Domain | Env Vars |
|---|---|---|---|
backend-brinxx | ghcr.io/midego1/magic-everse/commerce-backend:main | admin-brinxx.magiceverse.online | TENANT=brinxx |
backend-default | same image | admin-default.magiceverse.online | TENANT=default |
backend-logohorloge | same image | admin-logohorloge.magiceverse.online | TENANT=logohorloge FEATURES=2d-designer,aplt-techniques |
backend-bovisales | same image | admin-bovisales.magiceverse.online | TENANT=bovisales |
backend-demo | same image | admin-demo.magiceverse.online | TENANT=demo |
backend-desluis | same image | admin-desluis.magiceverse.online | TENANT=desluis |
backend-jodasign | same image | admin-jodasign.magiceverse.online | TENANT=jodasign FEATURES=extended-quotations |
backend-mondial | same image | admin-mondial.magiceverse.online | TENANT=mondial (real tenant, not symlink) |
backend-spranz | same image | admin-spranz.magiceverse.online | TENANT=spranz |
backend-toolvizion | same image | admin-toolvizion.magiceverse.online | TENANT=toolvizion |
backend-staging | same image :dev tag | staging.magiceverse.online | TENANT=staging |
Tenant Storefronts — 12 containers, 12 separate repos
| Service | Image (separate repo) | Domain |
|---|---|---|
sf-brinxx | ghcr.io/midego1/magic-sf-brinxx:main | brinxx.magiceverse.online |
sf-logohorloge | ghcr.io/midego1/magic-sf-logohorloge:main | logohorloge.magiceverse.online |
sf-jodasign | ghcr.io/midego1/magic-sf-jodasign:main | jodasign.magiceverse.online |
| … | each storefront has its own repo | … |
Storefronts are separate repos because each client has a unique design. They’re forked from a base template.
| Service | Source in Monorepo | Image | Domain |
|---|---|---|---|
pim-backend | services/pim/backend/ | ghcr.io/midego1/magic-everse/pim-backend:main | pim.magiceverse.online |
pim-storefront | services/pim/storefront/ | ghcr.io/midego1/magic-everse/pim-storefront:main | pim.magiceverse.online (different port/path) |
portal | services/portal/ | ghcr.io/midego1/magic-everse/portal:main | portal.magiceverse.online |
agent | services/agent/ | ghcr.io/midego1/magic-everse/agent:main | Runs behind tenant backends |
connector | services/connector/ | ghcr.io/midego1/magic-everse/connector:main | Internal service |
docs | services/docs/ | ghcr.io/midego1/magic-everse/docs:main | devdocs.magiceverse.online |
| Service | Source in Monorepo | Image | Domain / Usage |
|---|---|---|---|
logo-designer | services/logo-designer/ | ghcr.io/midego1/magic-everse/logo-designer:main | designer.spranz.de + embedded in storefronts |
modal | services/modal/ | ghcr.io/midego1/magic-everse/modal:main | Embedded widget in storefronts |
3d-viewer | services/3d-viewer/ | ghcr.io/midego1/magic-everse/3d-viewer:main | Embedded in storefronts |
editor | services/editor/ | ghcr.io/midego1/magic-everse/editor:main | Admin tool |
resize | services/resize/ | ghcr.io/midego1/magic-everse/resize:main | Internal image processing |
rembg | services/rembg/ | Standard danielgatis/rembg image | Internal background removal |
| Service | Image | Notes |
|---|---|---|
traefik | traefik:v3 | Reverse proxy, SSL, domain routing |
postgres | postgres:16 | One instance, separate DB per tenant |
redis | redis:7-alpine | One shared instance, key prefixes |
meilisearch | getmeili/meilisearch:v1 | One shared instance, tenant indices |
n8n | n8nio/n8n:latest | 1 instance (not 4) |
watchtower | containrrr/watchtower | Auto-deploys new images |
uptime-kuma | louislam/uptime-kuma:1 | Monitoring + alerts |
Total production containers: ~42 (vs ~60 today, but properly organized and on a real server)
What’s NOT in the Monorepo
Section titled “What’s NOT in the Monorepo”| What | Why Separate |
|---|---|
Storefronts (magic-sf-brinxx, etc.) | Each client has unique design. Different release cadence. Designer works on these independently. |
| Infrastructure config for Wayne’s home server | His dev setup is personal and shouldn’t be in the shared repo |
Services That Get Killed
Section titled “Services That Get Killed”| Current | Reason |
|---|---|
magic_beta_portal | Merged into main portal |
magic_tenant_portal | Merged into main portal |
magic_project_portal | Merged into main portal |
magic_management / magic_manager | Merged into main portal |
magic_n8n_2, magic_n8n_3, magic_n8n_clean | Keep 1 n8n, kill the rest |
magic_messageserver | Merged into mailer (or killed if redundant) |
magic_infrastructure_map | Static docs, move into services/docs/ |
| 11× Redis instances | Replaced by 1 shared Redis |
| 9× Meilisearch instances | Replaced by 1 shared Meilisearch |
Part 4: The Production Server — Built From Scratch
Section titled “Part 4: The Production Server — Built From Scratch”The Stack
Section titled “The Stack”No platform, no orchestration tool. Just proven, boring infrastructure:
| Component | Tool | Why |
|---|---|---|
| Reverse proxy + SSL | Traefik | Auto-discovers Docker containers, auto-SSL via Let’s Encrypt, routes domains. Zero config per new service. |
| Containers | Docker + Docker Compose | You already know it. Battle-tested. |
| Container registry | GitHub Container Registry (ghcr.io) | Free for private repos. Images built by GitHub Actions. |
| CI/CD | GitHub Actions | On push: detect what changed, build only affected images, push to ghcr.io. |
| Auto-deploy | Watchtower | Watches ghcr.io for new images. Pulls and restarts automatically. |
| Database | PostgreSQL 16 | One instance, separate database per tenant + PIM + portal. |
| Search | Meilisearch | One instance, tenant-prefixed indices. |
| Cache | Redis | One instance, key prefix per tenant. |
| Monitoring | Uptime Kuma | Checks all URLs every minute, alerts via email/Telegram. |
| Backups | Cron + pg_dump + offsite | Daily dumps to Backblaze B2/S3. |
Production Server Directory Structure
Section titled “Production Server Directory Structure”/opt/magic/├── docker-compose.traefik.yml # Reverse proxy + SSL├── docker-compose.infra.yml # PostgreSQL, Redis, Meilisearch├── docker-compose.tenants.yml # ALL tenant backends (same image, diff env vars)├── docker-compose.storefronts.yml # ALL storefronts (diff images)├── docker-compose.platform.yml # PIM, Portal, Agent, Connector, Docs├── docker-compose.tools.yml # Logo designer, Modal, 3D, Editor, Resize, Rembg├── docker-compose.monitoring.yml # Watchtower, Uptime Kuma, n8n├── .env # All secrets (POSTGRES_PASSWORD, MEILI_KEY, etc.)├── backups/│ └── backup.sh # Daily cron job└── letsencrypt/ # SSL certificates (auto-managed by Traefik)All compose files share a single Docker network (magic-network) so services can talk to each other by container name.
Compose Files
Section titled “Compose Files”services: traefik: image: traefik:v3 command: - --providers.docker=true - --providers.docker.exposedByDefault=false - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web - --certificatesresolvers.le.acme.email=admin@magiceverse.online - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./letsencrypt:/letsencrypt networks: - magic-network restart: always
networks: magic-network: name: magic-networkservices: postgres: image: postgres:16 environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres-data:/var/lib/postgresql/data networks: - magic-network restart: always
redis: image: redis:7-alpine command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru networks: - magic-network restart: always
meilisearch: image: getmeili/meilisearch:v1 environment: MEILI_MASTER_KEY: ${MEILI_MASTER_KEY} volumes: - meilisearch-data:/meili_data networks: - magic-network restart: always
volumes: postgres-data: meilisearch-data:
networks: magic-network: external: true# docker-compose.tenants.yml — same image, different env varsservices: backend-brinxx: image: ghcr.io/midego1/magic-everse/commerce-backend:main environment: TENANT: brinxx DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@postgres:5432/magic_b2b_brinxx REDIS_URL: redis://redis:6379/0 labels: - "traefik.enable=true" - "traefik.http.routers.be-brinxx.rule=Host(`admin-brinxx.magiceverse.online`)" - "traefik.http.routers.be-brinxx.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
backend-logohorloge: image: ghcr.io/midego1/magic-everse/commerce-backend:main environment: TENANT: logohorloge DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@postgres:5432/magic_b2b_logohorloge REDIS_URL: redis://redis:6379/0 FEATURES: "2d-designer,aplt-techniques,public-technique-api" labels: - "traefik.enable=true" - "traefik.http.routers.be-logohorloge.rule=Host(`admin-logohorloge.magiceverse.online`)" - "traefik.http.routers.be-logohorloge.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
backend-jodasign: image: ghcr.io/midego1/magic-everse/commerce-backend:main environment: TENANT: jodasign DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@postgres:5432/magic_b2b_jodasign REDIS_URL: redis://redis:6379/0 FEATURES: "extended-quotations,source-code-tracking" labels: - "traefik.enable=true" - "traefik.http.routers.be-jodasign.rule=Host(`admin-jodasign.magiceverse.online`)" - "traefik.http.routers.be-jodasign.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
# Same pattern for: default, bovisales, demo, desluis, mondial, spranz, toolvizion
backend-staging: image: ghcr.io/midego1/magic-everse/commerce-backend:dev # dev branch! environment: TENANT: staging DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@postgres:5432/magic_b2b_staging labels: - "traefik.enable=true" - "traefik.http.routers.be-staging.rule=Host(`staging.magiceverse.online`)" - "traefik.http.routers.be-staging.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
networks: magic-network: external: trueservices: pim-backend: image: ghcr.io/midego1/magic-everse/pim-backend:main environment: DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@postgres:5432/magic_pim labels: - "traefik.enable=true" - "traefik.http.routers.pim-be.rule=Host(`pim.magiceverse.online`)" - "traefik.http.routers.pim-be.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
pim-storefront: image: ghcr.io/midego1/magic-everse/pim-storefront:main labels: - "traefik.enable=true" - "traefik.http.routers.pim-sf.rule=Host(`products.magiceverse.online`)" - "traefik.http.routers.pim-sf.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
portal: image: ghcr.io/midego1/magic-everse/portal:main labels: - "traefik.enable=true" - "traefik.http.routers.portal.rule=Host(`portal.magiceverse.online`)" - "traefik.http.routers.portal.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
agent: image: ghcr.io/midego1/magic-everse/agent:main environment: REDIS_URL: redis://redis:6379/1 labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
connector: image: ghcr.io/midego1/magic-everse/connector:main labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
docs: image: ghcr.io/midego1/magic-everse/docs:main labels: - "traefik.enable=true" - "traefik.http.routers.docs.rule=Host(`devdocs.magiceverse.online`)" - "traefik.http.routers.docs.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
networks: magic-network: external: trueservices: logo-designer: image: ghcr.io/midego1/magic-everse/logo-designer:main labels: - "traefik.enable=true" - "traefik.http.routers.logo.rule=Host(`designer.spranz.de`)" - "traefik.http.routers.logo.tls.certresolver=le" - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
modal: image: ghcr.io/midego1/magic-everse/modal:main labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
3d-viewer: image: ghcr.io/midego1/magic-everse/3d-viewer:main labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
editor: image: ghcr.io/midego1/magic-everse/editor:main labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
resize: image: ghcr.io/midego1/magic-everse/resize:main labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
rembg: image: danielgatis/rembg:latest networks: [magic-network] restart: always
mailer: image: ghcr.io/midego1/magic-everse/mailer:main labels: - "com.centurylinklabs.watchtower.enable=true" networks: [magic-network] restart: always
networks: magic-network: external: trueservices: watchtower: image: containrrr/watchtower environment: WATCHTOWER_CLEANUP: "true" WATCHTOWER_POLL_INTERVAL: 60 WATCHTOWER_LABEL_ENABLE: "true" volumes: - /var/run/docker.sock:/var/run/docker.sock - ~/.docker/config.json:/config.json:ro restart: always
uptime-kuma: image: louislam/uptime-kuma:1 volumes: - uptime-data:/app/data labels: - "traefik.enable=true" - "traefik.http.routers.monitor.rule=Host(`monitor.magiceverse.online`)" - "traefik.http.routers.monitor.tls.certresolver=le" networks: [magic-network] restart: always
n8n: image: n8nio/n8n:latest environment: N8N_HOST: n8n.magiceverse.online N8N_PROTOCOL: https volumes: - n8n-data:/home/node/.n8n labels: - "traefik.enable=true" - "traefik.http.routers.n8n.rule=Host(`n8n.magiceverse.online`)" - "traefik.http.routers.n8n.tls.certresolver=le" networks: [magic-network] restart: always
volumes: uptime-data: n8n-data:
networks: magic-network: external: truePart 5: The Development Pipeline — Every Service
Section titled “Part 5: The Development Pipeline — Every Service”How It Works For ALL Services (not just the backend)
Section titled “How It Works For ALL Services (not just the backend)”┌────────────────────────────────────────────────────────┐│ WAYNE'S HOME SERVER ││ ││ ~/magic-everse/ ← ONE directory ││ ├── services/commerce-backend/ ← works on backend ││ ├── services/portal/ ← works on portal ││ ├── services/agent/ ← works on agent ││ ├── services/logo-designer/ ← works on designer ││ └── ... everything in one place ││ ││ Wayne makes changes anywhere ││ Claude Code: "commit and push" │└──────────────────────┬─────────────────────────────────┘ │ git push ▼┌────────────────────────────────────────────────────────┐│ GITHUB ACTIONS ││ ││ Detects WHAT changed using path filters: ││ ││ services/commerce-backend/ changed? ││ → Build ghcr.io/midego1/magic-everse/commerce-backend││ ││ services/portal/ changed? ││ → Build ghcr.io/midego1/magic-everse/portal ││ ││ services/agent/ changed? ││ → Build ghcr.io/midego1/magic-everse/agent ││ ││ services/logo-designer/ changed? ││ → Build ghcr.io/midego1/magic-everse/logo-designer ││ ││ Only changed services get rebuilt. Others untouched. │└──────────────────────┬─────────────────────────────────┘ │ pushes new image(s) to ghcr.io ▼┌────────────────────────────────────────────────────────┐│ PRODUCTION SERVER ││ ││ Watchtower detects new image(s) ││ Restarts ONLY the affected containers: ││ ││ commerce-backend changed? ││ → Restarts ALL 12 tenant backends (same image) ││ ││ portal changed? ││ → Restarts portal container only ││ ││ agent changed? ││ → Restarts agent container only ││ ││ Other containers untouched. │└────────────────────────────────────────────────────────┘GitHub Actions — Path-Based Builds
Section titled “GitHub Actions — Path-Based Builds”One workflow file that builds only what changed:
name: Build & Deploy
on: push: branches: [main, dev]
jobs: detect-changes: runs-on: ubuntu-latest outputs: commerce-backend: ${{ steps.changes.outputs.commerce-backend }} pim-backend: ${{ steps.changes.outputs.pim-backend }} pim-storefront: ${{ steps.changes.outputs.pim-storefront }} portal: ${{ steps.changes.outputs.portal }} agent: ${{ steps.changes.outputs.agent }} connector: ${{ steps.changes.outputs.connector }} logo-designer: ${{ steps.changes.outputs.logo-designer }} modal: ${{ steps.changes.outputs.modal }} 3d-viewer: ${{ steps.changes.outputs.3d-viewer }} editor: ${{ steps.changes.outputs.editor }} resize: ${{ steps.changes.outputs.resize }} moodshot: ${{ steps.changes.outputs.moodshot }} mailer: ${{ steps.changes.outputs.mailer }} docs: ${{ steps.changes.outputs.docs }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: changes with: filters: | commerce-backend: - 'services/commerce-backend/**' pim-backend: - 'services/pim/backend/**' pim-storefront: - 'services/pim/storefront/**' portal: - 'services/portal/**' agent: - 'services/agent/**' connector: - 'services/connector/**' logo-designer: - 'services/logo-designer/**' modal: - 'services/modal/**' 3d-viewer: - 'services/3d-viewer/**' editor: - 'services/editor/**' resize: - 'services/resize/**' moodshot: - 'services/moodshot/**' mailer: - 'services/mailer/**' docs: - 'services/docs/**'
build-service: needs: detect-changes runs-on: ubuntu-latest permissions: contents: read packages: write strategy: matrix: include: - service: commerce-backend context: services/commerce-backend changed: ${{ needs.detect-changes.outputs.commerce-backend }} - service: pim-backend context: services/pim/backend changed: ${{ needs.detect-changes.outputs.pim-backend }} - service: pim-storefront context: services/pim/storefront changed: ${{ needs.detect-changes.outputs.pim-storefront }} - service: portal context: services/portal changed: ${{ needs.detect-changes.outputs.portal }} - service: agent context: services/agent changed: ${{ needs.detect-changes.outputs.agent }} - service: connector context: services/connector changed: ${{ needs.detect-changes.outputs.connector }} - service: logo-designer context: services/logo-designer changed: ${{ needs.detect-changes.outputs.logo-designer }} - service: modal context: services/modal changed: ${{ needs.detect-changes.outputs.modal }} - service: 3d-viewer context: services/3d-viewer changed: ${{ needs.detect-changes.outputs.3d-viewer }} - service: editor context: services/editor changed: ${{ needs.detect-changes.outputs.editor }} - service: resize context: services/resize changed: ${{ needs.detect-changes.outputs.resize }} - service: docs context: services/docs changed: ${{ needs.detect-changes.outputs.docs }} steps: - if: matrix.changed == 'true' uses: actions/checkout@v4
- if: matrix.changed == 'true' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- if: matrix.changed == 'true' uses: docker/build-push-action@v5 with: context: ${{ matrix.context }} push: true tags: ghcr.io/midego1/magic-everse/${{ matrix.service }}:${{ github.ref_name }}What this means: Wayne changes 3 lines in the portal and pushes. Only the portal image gets rebuilt. The 12 tenant backends, PIM, agent, everything else — completely untouched.
Storefronts — Separate Pipeline
Section titled “Storefronts — Separate Pipeline”Each storefront repo has its own simple workflow (same 20-line GitHub Actions from before). The designer or Wayne pushes a storefront change → only that one storefront rebuilds.
Safety: dev → main Branch Flow
Section titled “Safety: dev → main Branch Flow”Wayne pushes to "dev" branch (default) → GitHub Actions builds images tagged :dev → Watchtower restarts staging containers only → Wayne/Michiel verify on staging.magiceverse.online
Michiel merges dev → main (via GitHub or Claude Code) → GitHub Actions builds images tagged :main → Watchtower restarts all production containers → All tenants updatedWayne never pushes directly to main. Claude Code is configured to always push to dev. Michiel reviews and merges.
Part 6: Wayne’s Home Server — Development Setup
Section titled “Part 6: Wayne’s Home Server — Development Setup”Wayne’s server keeps running, but ONLY for development. No more production traffic.
~/magic-everse/ ← Git clone of the monorepo├── services/│ ├── commerce-backend/ ← Wayne works here most of the time│ ├── portal/│ ├── agent/│ └── ...
~/storefronts/ ← Separate storefront repos├── magic-sf-brinxx/├── magic-sf-logohorloge/└── ...
Docker containers (dev only):├── development-backend ← Runs from local code, not ghcr.io├── development-storefront ← For testing├── postgres-dev ← Local dev databases├── redis-dev└── meilisearch-devWayne’s development docker-compose mounts local code for hot-reloading — changes appear instantly without rebuilding. This is exactly how he works today, just organized.
Part 7: Wayne’s Workflow — Before & After
Section titled “Part 7: Wayne’s Workflow — Before & After”Before (11 steps, touches 12+ directories)
Section titled “Before (11 steps, touches 12+ directories)”1. SSH into home server2. Open Claude Code in magic_development/backend/3. Build feature4. Test against development tenant5. Manually copy changed files to 11 other tenant folders6. Remember not to overwrite storefront files7. Remember jodasign needs sudo8. Remember logohorloge has unique files9. Rebuild each tenant's Docker container one by one10. Check nothing broke on each tenant11. If also changing portal/agent/tools: repeat in those directoriesAfter (4 steps, one directory)
Section titled “After (4 steps, one directory)”1. SSH into home server2. Open Claude Code in ~/magic-everse/3. Build feature (backend, portal, agent, whatever — it's all here)4. Say "commit and push" — doneWhat happens automatically after step 4:
- Claude Code commits and pushes to
devbranch - GitHub Actions detects what changed, builds only those images
- Watchtower on production restarts only affected staging containers
- Michiel verifies staging, merges to
main - All production containers update
Wayne’s world is ONE directory. He works on anything — backend, portal, agent, logo designer, 3D viewer, docs — it’s all there. One commit captures everything. No syncing. No copying. No remembering rules.
Part 8: New Tenant Onboarding
Section titled “Part 8: New Tenant Onboarding”Before (~1 day)
Section titled “Before (~1 day)”Clone directories, modify configs, set up Nginx, SSL certs, create database, build containers, add to sync script, update guardrails doc…
After (~30 minutes)
Section titled “After (~30 minutes)”-
Create database
Terminal window docker exec postgres createdb -U postgres magic_b2b_newtenant -
Add tenant config — create
services/commerce-backend/tenants/newtenant.json:{ "name": "New Tenant", "features": [], "domain": "newtenant.magiceverse.online" } -
Add to compose — 15 lines in
docker-compose.tenants.yml:backend-newtenant:image: ghcr.io/midego1/magic-everse/commerce-backend:mainenvironment:TENANT: newtenantDATABASE_URL: postgres://...newtenantlabels:- "traefik.http.routers.be-newtenant.rule=Host(`admin-newtenant.magiceverse.online`)"- "traefik.http.routers.be-newtenant.tls.certresolver=le"- "com.centurylinklabs.watchtower.enable=true"networks: [magic-network]restart: always -
Fork the storefront — clone
magic-sf-base, customize branding -
Point DNS — add CNAME records for
admin-newtenantandnewtenant -
docker compose up -d— Traefik handles SSL automatically -
Done. New tenant live.
Part 9: Container Count — Before & After
Section titled “Part 9: Container Count — Before & After”| Environment | Before | After |
|---|---|---|
| Wayne’s home server | ~60 (dev + prod mixed) | ~5 (dev only) |
| Production server | 0 (doesn’t exist) | ~42 (organized) |
| Total | ~60 on one home server | ~47 across two servers, properly separated |
Part 10: Migration Steps — In Order
Section titled “Part 10: Migration Steps — In Order”Step 1: Clean House
Section titled “Step 1: Clean House”- Answer all open questions (portals, n8n, dead projects)
- Archive dead projects to
_archive/ - Delete
.bak_*files everywhere - Consolidate n8n to 1 instance
- Consolidate portals into 1
- Resolve
mondial→jodasignsymlink
Step 2: Create the Monorepo
Section titled “Step 2: Create the Monorepo”- Create
magic-everserepo on GitHub - Move
magic_development/backend/→services/commerce-backend/ - Merge logohorloge/jodasign unique features behind feature flags
- Move
magic_pim/→services/pim/ - Move
magic_portal/→services/portal/ - Move
magic_agent/→services/agent/ - Move
magic_connector/→services/connector/ - Move
magic_logo/→services/logo-designer/ - Move
magic_modal/→services/modal/ - Move
magic_3d/→services/3d-viewer/ - Move
magic_editor/→services/editor/ - Move
magic_resize/→services/resize/ - Move
magic_docs/starlight/→services/docs/ - Move remaining active services
- Ensure every service has a working Dockerfile
- Set up GitHub Actions workflow (path-based builds)
- Test every image builds successfully
Step 3: Create Storefront Repos
Section titled “Step 3: Create Storefront Repos”- Create
magic-sf-basefrom development storefront - Fork per client, preserve customizations
- Set up GitHub Actions per storefront repo
- Ensure each builds and runs
Step 4: Set Up Production Server
Section titled “Step 4: Set Up Production Server”- Install Docker
- Create
magic-network - Deploy Traefik
- Deploy PostgreSQL + Redis + Meilisearch
- Migrate all databases from home server
- Deploy tenant backends (from ghcr.io images)
- Deploy storefronts
- Deploy platform services (PIM, portal, agent, etc.)
- Deploy tools (logo designer, editor, resize, 3d, etc.)
- Set up Watchtower
- Set up Uptime Kuma
- Set up backup cron
- Deploy staging tenant on
devbranch
Step 5: Go Live
Section titled “Step 5: Go Live”- Point DNS to production server
- Verify ALL services work (not just backends — portal, PIM, agents, designer, 3d, etc.)
- Stop production containers on home server
- Home server = dev only
Part 11: Notes
Section titled “Part 11: Notes”- Mondial is a new customer — gets its own database, tenant config, and deployment. The current symlink to jodasign is temporary and will be replaced with a proper tenant setup.
- Everything gets migrated into the monorepo as-is. Dead projects sit quietly in
services/and never get built unless touched. Active projects get Dockerfiles and deploy automatically. No need to audit what’s alive before starting.
Part 10: Open Questions (Must Answer Before Starting)
Section titled “Part 10: Open Questions (Must Answer Before Starting)”| # | Question | Why It Matters |
|---|---|---|
| 1 | What are the 4 portals and are they all active? | Determines merge scope |
| 2 | Why 4 n8n instances? | Can we kill 3? |
| 3 | What is master_magic? | Running with unclear purpose |
| 4 | What is magiceverse_evolved? | New app — replacing something? |
| 5 | Is mondial → jodasign intentional long-term? | Needs real tenant |
| 6 | magic_3d vs magic_3d_mv — both needed? | Potential dead project |
| 7 | magic_management vs magic_manager — both needed? | Overlap |
| 9 | magic_messageserver vs mailer — both needed? | Overlap |
| 10 | What is preflight-service? | Running, unknown purpose |
| 11 | Are maw_* projects active? | 3 MAW-related projects |
| 12 | What is simscan? | Unknown purpose |
Last updated: 2026-03-31 Generated by platform scalability analysis