# 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 `mkcert` on 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.yml` when ready) - Zulip (resource-heavy — added separately when chat features are needed) ## Critical setup invariants 1. **All code in English** — variable names, comments, schema fields, routes, function names. UI strings may be Danish for end users only. 2. **TypeScript everywhere** in apps and services. 3. **mkcert TLS certs** — the user has already run `mkcert -install` and generated wildcard certs. They will be placed in `infrastructure/docker-compose/certs/`. 4. **All hostnames use `.dezky.local`** in development. `/etc/hosts` entries required (see `scripts/setup-hosts.sh`). 5. **PostgreSQL is shared** among Authentik and OCIS. Each gets its own database and user (see `configs/postgres/init.sql`). 6. **MongoDB is dedicated** to the portal application data (matches user's existing stack pattern: Målerportal, TurtleLootLine). 7. **Secrets are .env-based** for dev — production will use SOPS/sealed-secrets in k3s. ## Expected user environment - macOS (uses `brew` for installs) - Docker Desktop or OrbStack running - mkcert installed and root CA trusted - 16+ GB RAM (stack uses ~10 GB at peak) - `pnpm` for Node workspaces - `git` for 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) ```bash # 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: 1. Check if `infrastructure/docker-compose/certs/dezky.local.pem` exists 2. If not, run mkcert for the wildcard domain: ```bash 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: ```bash 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) ```bash 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: 1. Create admin user (akadmin) 2. Configure OIDC providers for: ocis, portal, stalwart 3. 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 load - `https://auth.dezky.local` — Authentik login screen - `https://files.dezky.local` — OCIS should redirect to Authentik for SSO - `https://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 -CAROOT` matches 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 postgres` for `database 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 -install` has been run AND browser has been restarted. See `docs/TROUBLESHOOTING.md` for detailed solutions. ## After local dev works 1. Build out the Nuxt portal (`apps/portal`) — start with auth flow via Authentik OIDC 2. Build the platform API (`services/platform-api`) — first endpoint: create tenant 3. Wire portal → platform-api → Authentik/OCIS/Stalwart admin APIs 4. Add Zulip + Jitsi when ready (`docker-compose.optional.yml`) 5. When portal MVP is solid → migrate to Hetzner AX41 production --- **Last updated:** 2026-05-23