Files
dezky/docs/SERVICES.md
T
Ronni Baslund 4b71b5751f
ci / changes (push) Successful in 4s
ci / tc_operator (push) Has been skipped
ci / tc_website (push) Has been skipped
ci / test_platform_api (push) Has been skipped
ci / tc_platform_api (push) Has been skipped
ci / build_portal (push) Has been skipped
ci / build_booking (push) Has been skipped
ci / build_operator (push) Has been skipped
ci / build_platform_api (push) Has been skipped
ci / tc_portal (push) Has been skipped
ci / tc_booking (push) Has been skipped
ci / build_zpush (push) Has been skipped
ci / deploy (push) Successful in 28s
fix(mail): chown zpush state on pod start — root-owned files break sync
A root-run z-push-admin (kubectl exec defaults to root) left a
root-owned 'users' file on the state PVC; Apache runs as www-data, so
every request 500'd with 'Not possible to write to the configured
state directory'. An initContainer now normalizes ownership on every
start (state is disposable, ownership isn't precious), and the docs
say to exec z-push-admin as www-data.
2026-06-12 15:46:31 +02:00

7.5 KiB

Services Reference

Per-service details: what each one does, where its config lives, and how to debug it.

Traefik

Image: traefik:v3.2 Container: dezky-traefik URL: https://traefik.dezky.local (dashboard) Purpose: Reverse proxy, TLS termination, service discovery via Docker labels

Config:

  • Static: configs/traefik/traefik.yml
  • Dynamic: configs/traefik/dynamic.yml (TLS certs)
  • Certs: certs/dezky.local.pem + certs/dezky.local-key.pem

Debug:

docker compose logs -f traefik
# Open https://traefik.dezky.local for dashboard

PostgreSQL

Image: postgres:16-alpine Container: dezky-postgres Internal hostname: postgres Purpose: Shared RDBMS for Authentik and OCIS (future)

Databases:

  • authentik (owner: authentik)
  • ocis (owner: ocis, reserved for future use)

Debug:

# Shell access
docker compose exec postgres psql -U postgres

# Check users
\du

# Check databases
\l

# Connect to specific DB
\c authentik

MongoDB

Image: mongo:7 Container: dezky-mongo Internal hostname: mongo Purpose: Portal application data

Connection:

mongodb://root:${MONGO_ROOT_PASSWORD}@mongo:27017/dezky?authSource=admin

Debug:

docker compose exec mongo mongosh -u root -p $(grep MONGO_ROOT_PASSWORD .env | cut -d= -f2)

Redis

Image: redis:7-alpine Container: dezky-redis Internal hostname: redis Purpose: Cache and session store (used by Authentik)

Debug:

docker compose exec redis redis-cli -a $(grep REDIS_PASSWORD .env | cut -d= -f2)
> KEYS *
> INFO

Authentik

Image: ghcr.io/goauthentik/server:2025.10 Containers: dezky-authentik (server) + dezky-authentik-worker URL: https://auth.dezky.local Purpose: Identity provider, SSO, MFA

First-time setup:

API:

Debug:

docker compose logs -f authentik-server authentik-worker

# Check API health
curl https://auth.dezky.local/-/health/ready/

See docs/AUTHENTIK-SETUP.md for OIDC configuration steps.


Stalwart Mail

Image: stalwartlabs/mail-server:latest Container: dezky-stalwart URL: https://mail.dezky.local Purpose: Mail server (SMTP/IMAP/JMAP/CalDAV/CardDAV — ActiveSync comes from the separate zpush gateway, see below)

Ports exposed:

  • 25 (SMTP)
  • 465 (SMTPS)
  • 587 (Submission)
  • 143 (IMAP)
  • 993 (IMAPS)
  • 4190 (ManageSieve)

Config: configs/stalwart/config.toml Data: Docker volume dezky_stalwart_data

Admin login:

  • User: admin
  • Password: STALWART_ADMIN_PASSWORD from .env

Debug:

docker compose logs -f stalwart

# Test SMTP
swaks --to test@dezky.local --from sender@example.com --server mail.dezky.local:25

# Check ports
docker compose port stalwart 25

Z-Push (EAS gateway)

Image: built from services/zpush (Z-Push 2.6.4, AGPLv3 — see services/zpush/LICENSE-NOTES.md) Container: dezky-zpush URL: https://mail.dezky.local/Microsoft-Server-ActiveSync (+ EAS autodiscover on https://autodiscover.dezky.local) Purpose: Exchange ActiveSync gateway in front of Stalwart — "Exchange" accounts on iOS/Android native Mail/Calendar get two-way mail + calendar sync (IMAP + CalDAV fan-out via BackendCombined). Contacts are NOT bundled yet: the combined login is all-or-nothing and Stalwart 404s addressbook homes that were never created — re-enable CardDAV once platform-api provisions a default address book at mailbox creation.

Protocol reality check: EAS 14.1. Covers native mobile clients; NOT the Outlook mobile app (requires EAS 16.1) and not new Outlook for Windows (no EAS at all). Classic Outlook on Windows syncs calendars against /dav with the free Outlook CalDAV Synchronizer add-in instead.

Auth: pure passthrough — the device's Basic credentials (mailbox password or app password) go straight to Stalwart. No secrets stored; zpush_state volume holds only resyncable device state.

Debug:

docker compose logs -f zpush

# Unauthenticated probe (expect 401 with realm="ZPush")
curl -k -i -X OPTIONS https://mail.dezky.local/Microsoft-Server-ActiveSync

# Authenticated: advertised EAS versions in MS-ASProtocolVersions header
curl -k -i -u user@tenant.tld:app-password -X OPTIONS \
  https://mail.dezky.local/Microsoft-Server-ActiveSync

# Per-device sync state. ALWAYS run as www-data — a root-run z-push-admin
# leaves root-owned state files that 500 every request ("Not possible to
# write to the configured state directory"). The prod pod has an
# initContainer that re-chowns the state dir on start as a backstop.
docker exec -u www-data dezky-zpush php /usr/share/z-push/z-push-admin.php -a list

OCIS

Image: owncloud/ocis:7.0 Container: dezky-ocis URL: https://files.dezky.local Purpose: File storage, sharing, sync

OIDC config:

  • Issuer: https://auth.dezky.local/application/o/ocis/
  • Client ID: ocis-web (configured in Authentik)
  • Auto-provision: enabled (creates OCIS user on first SSO login)

Admin login:

  • User: admin
  • Password: OCIS_ADMIN_PASSWORD from .env

Storage backend:

  • Dev: local filesystem inside volume dezky_ocis_data
  • Prod: will switch to S3 (Hetzner Object Storage)

Debug:

docker compose logs -f ocis

# Health check
curl -k https://files.dezky.local/

Collabora

Image: collabora/code:latest Container: dezky-collabora URL: https://office.dezky.local Purpose: Office document editing inside OCIS

Integration with OCIS:

  • OCIS must be configured to use Collabora as its office editor
  • See: OCIS app config → "wopiserver"

Debug:

docker compose logs -f collabora

# Discovery endpoint (used by OCIS)
curl -k https://office.dezky.local/hosting/discovery

Portal (Nuxt 3)

Container: dezky-portal URL: https://app.dezky.local Source: apps/portal/ Purpose: Customer-facing portal, launcher, custom webmail

Stack:

  • Nuxt 3
  • Vue 3 + TypeScript
  • Vite dev server
  • pnpm for dependencies

Hot reload:

  • File changes in apps/portal/ trigger HMR automatically
  • Vite watches via polling (configured in nuxt.config.ts)

Environment:

  • NUXT_PUBLIC_AUTH_URL: Authentik URL (client-side)
  • NUXT_API_BASE: platform-api URL (server-side)
  • MONGODB_URI: MongoDB connection string

Debug:

docker compose logs -f portal

# Shell into container
docker compose exec portal sh
> pnpm dev

Platform API (NestJS)

Container: dezky-platform-api Port: 3001 (also exposed via Traefik at api.dezky.local) Source: services/platform-api/ Purpose: Platform control plane — tenants, partners, users, subscriptions, provisioning orchestration, billing webhooks

Endpoints to implement:

  • POST /tenants — Create tenant
  • GET /tenants/:id — Get tenant
  • PATCH /tenants/:id — Update tenant
  • POST /tenants/:id/users — Add user to tenant
  • POST /webhooks/stripe — Billing events

Environment:

  • MONGODB_URI: Portal data store
  • AUTHENTIK_API_URL + AUTHENTIK_API_TOKEN
  • STALWART_API_URL + STALWART_ADMIN_USER/PASSWORD
  • OCIS_API_URL

Debug:

docker compose logs -f platform-api

# Test health endpoint
docker compose exec platform-api wget -qO- http://localhost:3001/health