O.0 prep from OPERATOR-PLAN.md. Mechanical refactor before adding partner management and operator-specific endpoints. The service now owns more than just provisioning orchestration (it'll soon own partners, tenant lifecycle actions, multi-audience JWT validation), so the name 'platform-api' reflects its scope better. What changed: - Directory: services/provisioning/ -> services/platform-api/ - Package: @dezky/provisioning -> @dezky/platform-api - Docker: container_name dezky-provisioning -> dezky-platform-api; compose service key 'provisioning' -> 'platform-api'; volume provisioning_node_modules -> platform_api_node_modules - Portal: PROVISIONING_INTERNAL_URL env var -> PLATFORM_API_INTERNAL_URL, default URL http://provisioning:3001 -> http://platform-api:3001 in all three proxy routes (me.get.ts, tenants/index.post.ts, tenants/[slug]/ reconcile.post.ts), plus NUXT_API_BASE updated - Health endpoint service identifier and main.ts log lines updated to 'dezky-platform-api' - Docs swept: README, CLAUDE.md, SERVICES.md, AUTHENTIK-SETUP.md, NEXT-STEPS.md, TROUBLESHOOTING.md, OPERATOR-PLAN.md, traefik/dynamic.yml What deliberately stays: - Internal module names ProvisioningService / ProvisioningModule (those describe an orchestration sub-concern, not the service's purpose) - Tenant.provisioningStatus / provisioningErrors field names (state per integration, not service name) - File services/platform-api/src/tenants/provisioning.service.ts - 'Hetzner provisioning' references in production-prep docs (infrastructure provisioning, unrelated) Verified end-to-end after rename: /api/me returns 200 with profile + 2 tenants + subscription, /api/tenants/dezky/reconcile returns 200 with Authentik integration still ok. OPERATOR-PLAN.md O.0 checkboxes ticked.
11 KiB
Dezky — Local Development Environment Setup
For Claude Code: This is a complete handover document for spinning up the Dezky platform locally for development. Everything you need to know is here.
Mission for this session
The user (Ronni) has already:
- Selected brand: Dezky (dezky.com pending purchase)
- Run
mkcerton macOS to generate local TLS certificates - Decided on production target: Hetzner AX41-NVMe + Object Storage + Storage Box
- Defined the full stack (see Stack section)
Your job: Get the entire Dezky platform running locally via Docker Compose so Ronni can develop against it. The user has solid technical fluency (NestJS, Nuxt, MongoDB, Docker, Kubernetes) — skip basic explanations, but be thorough on Dezky-specific configuration.
Stack — what we're spinning up
All components are Apache 2.0 / MIT licensed for clean commercial multi-tenant hosting.
| Service | Image | Purpose | Local URL |
|---|---|---|---|
| Traefik | traefik:v3.2 |
Reverse proxy + TLS termination | https://traefik.dezky.local:8443 |
| PostgreSQL | postgres:16-alpine |
Shared RDBMS for Authentik, OCIS | (internal) |
| MongoDB | mongo:7 |
Portal application data | (internal) |
| Redis | redis:7-alpine |
Cache + session store | (internal) |
| Authentik | ghcr.io/goauthentik/server:2025.10 |
Identity provider, OIDC/SAML | https://auth.dezky.local |
| Stalwart Mail | stalwartlabs/mail-server:latest |
Mail (SMTP/IMAP/JMAP/CalDAV) | https://mail.dezky.local |
| OCIS | owncloud/ocis:7.0 |
File storage (S3-compatible backend) | https://files.dezky.local |
| Collabora | collabora/code:latest |
Office document editor inside OCIS | https://office.dezky.local |
| Portal stub | (built from ./apps/portal) |
Nuxt 3 customer portal | https://app.dezky.local |
| Platform API | (built from ./services/platform-api) |
NestJS service · tenants/partners/users/provisioning orchestration | api.dezky.local (+ internal port 3001) |
NOT included in this dev setup (added in later phases):
- Jitsi Meet (4-5 sub-containers — see
docker-compose.optional.ymlwhen ready) - Zulip (resource-heavy — added separately when chat features are needed)
Critical setup invariants
- All code in English — variable names, comments, schema fields, routes, function names. UI strings may be Danish for end users only.
- TypeScript everywhere in apps and services.
- mkcert TLS certs — the user has already run
mkcert -installand generated wildcard certs. They will be placed ininfrastructure/docker-compose/certs/. - All hostnames use
.dezky.localin development./etc/hostsentries required (seescripts/setup-hosts.sh). - PostgreSQL is shared among Authentik and OCIS. Each gets its own database and user (see
configs/postgres/init.sql). - MongoDB is dedicated to the portal application data (matches user's existing stack pattern: Målerportal, TurtleLootLine).
- Secrets are .env-based for dev — production will use SOPS/sealed-secrets in k3s.
Expected user environment
- macOS (uses
brewfor installs) - Docker Desktop or OrbStack running
- mkcert installed and root CA trusted
- 16+ GB RAM (stack uses ~10 GB at peak)
pnpmfor Node workspacesgitfor version control
If anything is missing, instruct the user with the exact brew install command.
File structure
dezky/
├── CLAUDE.md # This file
├── README.md # Human-facing setup guide
├── .env.example # All required env vars
├── .gitignore
├── apps/
│ └── portal/ # Nuxt 3 portal (stub for now)
├── services/
│ └── platform-api/ # NestJS service · platform control plane
├── packages/ # Shared TypeScript packages (empty for now)
├── infrastructure/
│ └── docker-compose/
│ ├── docker-compose.yml # Main stack
│ ├── docker-compose.optional.yml # Jitsi + Zulip (later)
│ ├── certs/ # mkcert TLS certs go here
│ │ ├── dezky.local.pem # User places mkcert output here
│ │ └── dezky.local-key.pem
│ └── configs/
│ ├── traefik/
│ │ ├── traefik.yml # Static config
│ │ └── dynamic.yml # TLS + middleware
│ ├── stalwart/
│ │ └── config.toml # Mail server config
│ ├── postgres/
│ │ └── init.sql # Create DBs/users
│ ├── authentik/
│ │ └── blueprints/ # (Optional) Pre-configured flows
│ └── ocis/
│ └── (config files mounted at runtime)
├── scripts/
│ ├── bootstrap.sh # One-command setup
│ ├── setup-hosts.sh # /etc/hosts entries
│ ├── setup-certs.sh # Copy mkcert certs to right place
│ └── reset.sh # Nuke and start fresh
└── docs/
├── SERVICES.md # Per-service reference
├── AUTHENTIK-SETUP.md # First-time Authentik walkthrough
├── TROUBLESHOOTING.md # Common issues
└── NEXT-STEPS.md # What to do after setup works
Sequence for Claude Code to execute
When the user runs claude in the project directory, walk through:
Phase 1: Verify environment (5 min)
# Check that required tools are installed
docker --version # Need 24.0+
docker compose version # Need v2
mkcert -version # Need 1.4+
pnpm --version # Need 9+
node --version # Need 20+
If anything is missing, halt and instruct user with brew install commands.
Phase 2: Cert setup (2 min)
The user has already run mkcert -install. We need to:
- Check if
infrastructure/docker-compose/certs/dezky.local.pemexists - If not, run mkcert for the wildcard domain:
cd infrastructure/docker-compose/certs mkcert "*.dezky.local" "dezky.local" "localhost" "127.0.0.1" "::1" mv ./_wildcard.dezky.local+4.pem dezky.local.pem mv ./_wildcard.dezky.local+4-key.pem dezky.local-key.pem
Phase 3: DNS setup (2 min)
Run scripts/setup-hosts.sh which adds these to /etc/hosts (requires sudo):
127.0.0.1 dezky.local app.dezky.local auth.dezky.local
127.0.0.1 mail.dezky.local files.dezky.local meet.dezky.local
127.0.0.1 chat.dezky.local office.dezky.local
127.0.0.1 traefik.dezky.local
Phase 4: Environment variables (2 min)
Copy .env.example to .env and generate secure random values:
cp .env.example .env
# Generate 64-char hex for each *_SECRET / *_KEY
openssl rand -hex 32 # run multiple times, paste into .env
Phase 5: First boot (10 min)
cd infrastructure/docker-compose
docker compose pull # Get all images first
docker compose up -d postgres redis mongo
# Wait for healthchecks
docker compose logs -f postgres
# When ready, start identity layer
docker compose up -d authentik-server authentik-worker
# Wait for Authentik to be ready
docker compose logs -f authentik-server
# Then start everything else
docker compose up -d
Phase 6: Authentik initial setup (15 min)
Open https://auth.dezky.local and walk through:
- Create admin user (akadmin)
- Configure OIDC providers for: ocis, portal, stalwart
- Test SSO end-to-end
See docs/AUTHENTIK-SETUP.md for the exact steps.
Phase 7: Verify it all works (5 min)
https://app.dezky.local— Portal landing page should loadhttps://auth.dezky.local— Authentik login screenhttps://files.dezky.local— OCIS should redirect to Authentik for SSOhttps://mail.dezky.local— Stalwart admin UI
Important context for working on this project
Stack rationale (DO NOT suggest replacing components)
These choices were made deliberately after extensive license/architecture research:
- Stalwart over Mailcow: Modern Rust, ActiveSync built-in, JMAP support, single binary
- OCIS over Nextcloud: Apache 2.0 vs AGPL+trademark fees for whitelabel
- Zulip over Element/Mattermost/Rocket.Chat: Only truly open-core-free chat option
- Authentik over Keycloak: Better multi-tenancy, MIT license, simpler config
- Hetzner over AWS/GCP: 100% EU sovereignty pitch — this is core to the business
Code conventions
- TypeScript strict mode in apps/services
- English only in all code identifiers, comments, schema fields
- Conventional Commits for git messages:
feat:,fix:,chore:,docs: - Prefer prose comments over heavy JSDoc — explain why, not what
- MongoDB for portal app data (consistent with Målerportal, TurtleLootLine)
- PostgreSQL for services that require it (Authentik, OCIS)
Production target (for reference, not deploy now)
Eventually moves to single Hetzner AX41-NVMe (€39/mo) with:
- Stalwart on bare-metal (not Docker)
- k3s for all other services
- Hetzner Object Storage (€5/mo) for OCIS S3 backend
- Storage Box BX11 (€3.20/mo) for Restic backups
- Storage Box BX11 in Helsinki (€3.20/mo) for DR
But locally, everything runs in Docker Compose with mkcert TLS.
What to do if stuck
- TLS not working: Verify
mkcert -CAROOTmatches what Docker has access to. On macOS, the root CA must be trusted in the system keychain. - Authentik won't start: PostgreSQL needs to be fully ready first. Check
docker compose logs postgresfordatabase system is ready to accept connections. - OCIS OIDC fails: Authentik issuer URL must be reachable from inside the OCIS container. Use the Docker network hostname, not the public URL.
- Stalwart port 25 conflicts: macOS may have postfix running. Disable with
sudo launchctl unload /System/Library/LaunchDaemons/org.postfix.master.plist. - Cert errors in browser: Make sure
mkcert -installhas been run AND browser has been restarted.
See docs/TROUBLESHOOTING.md for detailed solutions.
After local dev works
- Build out the Nuxt portal (
apps/portal) — start with auth flow via Authentik OIDC - Build the platform API (
services/platform-api) — first endpoint: create tenant - Wire portal → platform-api → Authentik/OCIS/Stalwart admin APIs
- Add Zulip + Jitsi when ready (
docker-compose.optional.yml) - When portal MVP is solid → migrate to Hetzner AX41 production
Last updated: 2026-05-23