Files
Ronni Baslund 5407c04682 docs: feature-flag usage guide + cross-links
New docs/FEATURE-FLAGS.md captures when to add a flag, where the moving
parts live, how to use useFeatureFlag from app code, the 4 states + 4
scope axes, kill-switch flow, naming conventions, and the parts we know
aren't built yet (partnerSlug eval context, user-level flags, audit-log
integration, server-side cache).

CLAUDE.md gets a one-line convention entry under "Code conventions" so
future devs notice it when grepping for code rules. NEXT-STEPS.md is
updated: the feature-flag backend follow-up is now ticked done with a
pointer to FEATURE-FLAGS.md for the remaining sub-tasks, and the
"What landed" section reflects the real Infrastructure + Flags pages
and the notification drawer.
2026-05-24 19:29:24 +02:00

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 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)

# 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:
    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:

  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)
  • Feature flags ship through useFeatureFlag('key'), NOT hardcoded if (env === ...) checks. Risky / plan-gated / kill-switchable features go behind a flag. See docs/FEATURE-FLAGS.md for when to add one, how to use the composable, and the 4 states / 4 scope axes.

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