Klyvra Documentation
Everything you need to deploy, configure, and manage your self-hosted productivity suite.
Quick Start Guide
From zero to a running stack in 3 commands. Takes about 5 minutes.
BeginnerYour First Login
Create an admin account, log into every app, and understand the SSO flow.
BeginnerRBAC & Group Mapping
Define groups once, have permissions auto-propagate everywhere. The core feature.
BeginnerAdding Users & Groups
Step-by-step: create users, assign them to groups, watch access appear across apps.
BeginnerDeploying with Helm
Deploy on Kubernetes with the Klyvra Helm chart. Declarative RBAC in values.yaml.
IntermediateProduction Deployment
Hardware requirements, firewall, email DNS setup, backup/restore, and security hardening.
IntermediateCustom Domain & TLS
Move from localhost to a real domain with automatic HTTPS via Let's Encrypt.
IntermediateIndustry Presets
Pre-built RBAC configs for software houses, agencies, law firms, and more.
BeginnerArchitecture
Hub-and-spoke SSO, adapters, blueprints, and how the pieces fit together.
ReferenceTroubleshooting
Common issues and how to fix them: ports, DNS, SSO, and more.
ReferenceQuick Start Guide
From zero to a running productivity suite in under 5 minutes.
Prerequisites
- Docker Engine β₯ 24.0 with Compose v2 (Docker Desktop works too)
opensslandmake(pre-installed on most Linux/macOS systems)- 8 GB RAM minimum β all 10 apps plus their databases run simultaneously
- Port 80 free (or choose another port β see below)
Three Commands
1 Clone the repository
git clone https://github.com/klyvra-org/klyvra.git && cd klyvra
2 Generate configuration
make bootstrap
This creates a .env file with 30+ randomly generated secrets (database passwords, OIDC client secrets, API keys). You never need to touch these.
make bootstrap HTTP_PORT=8080 instead. All URLs will include the port automatically.
3 Start everything
make up
This builds custom images, starts 35 containers, waits for health checks, and runs init containers. Takes about 2β3 minutes on first run.
What You Get
When make up finishes, these URLs are live:
| App | URL | What it does |
|---|---|---|
| Authentik | auth.localhost | Identity & SSO admin |
| Outline | docs.localhost | Wiki & knowledge base |
| Mattermost | chat.localhost | Team chat & channels |
| Gitea | git.localhost | Git hosting & CI/CD |
| Seafile | files.localhost | File sync & share |
| Kimai | time.localhost | Time tracking |
| Vikunja | tasks.localhost | Task management |
| Vaultwarden | vault.localhost | Password manager |
| Stalwart | groupware.localhost | Email, calendar, contacts |
| OnlyOffice | office.localhost | Document editing |
Your First Login
Create an admin account, understand the SSO flow, and log into every app.
1 Log In as Authentik Admin
Open http://auth.localhost in your browser. Log in with:
- Username:
akadmin - Password: the value of
AUTHENTIK_BOOTSTRAP_PASSWORDin your.envfile
To find the password:
grep AUTHENTIK_BOOTSTRAP_PASSWORD .env
2 Explore the Admin Panel
After creating your account, go to http://auth.localhost/if/admin/. You'll see:
- Applications β all 10 apps are pre-configured
- Providers β 7 OIDC + 1 SAML provider, all wired up
- Users β just your admin account so far
- Groups β ready for you to create your company's structure
3 Log Into an App
Go to any app URL, for example docs.localhost (Outline). You'll see a login page with an SSO button:
- Outline β "Continue with Klyvra"
- Mattermost β "Login with Klyvra"
- Gitea β "Sign in with Klyvra"
- Seafile β "Single Sign-On"
- Kimai β "Login with Klyvra"
- Vikunja β "Klyvra"
- Vaultwarden β "Enterprise Single Sign-On" (enter any email β SSO redirects to Authentik)
Click the SSO button β you're redirected to Authentik β log in β redirected back to the app, logged in. That's the flow for all apps.
4 Stalwart Mail
Stalwart is slightly different β mailboxes are created automatically on first SSO login (JIT provisioning). After logging in at groupware.localhost, your mailbox is ready for IMAP/SMTP at:
- IMAP:
groupware.localhost:143 - SMTP:
groupware.localhost:587 - Username: your Authentik email
5 What's Next
Now that you can log in, the next step is setting up your company's group structure:
- Adding Users & Groups β create real users and assign them to groups
- RBAC & Group Mapping β understand how groups translate to per-app permissions
RBAC & Group Mapping
The core feature: define groups once in Authentik, have permissions propagate to every app.
How It Works
Klyvra uses a hub-and-spoke model for access control:
- You create a group in Authentik (e.g., "engineering")
- Authentik fires a webhook to all 5 adapters
- Each adapter creates the matching resource in its app:
- Gitea β creates an organization
- Mattermost β creates a channel
- Outline β creates a group (for collection permissions)
- Seafile β creates a group (for library sharing)
- Stalwart β creates a mailing list
- When you add a user to the group, adapters sync membership
- The user's role in each app is determined by the role mapping config
Role Mapping
Each adapter reads a ROLE_MAPPING environment variable (JSON) that maps group names to app-specific roles:
# Gitea adapter example
ROLE_MAPPING='{"engineering": "owner", "management": "read", "hr": null}'
| Value | Effect |
|---|---|
"owner", "admin", etc. | App-specific role name β see table below |
null | Exclude β this group is NOT synced to this app at all |
| (group not listed) | Default role for the app (usually member) |
Available Roles Per App
| App | Roles | Default |
|---|---|---|
| Gitea | owner Β· admin Β· write Β· read | owner |
| Mattermost | admin Β· member | member |
| Outline | member | member |
| Seafile | member | member |
| Kimai | super_admin Β· teamlead Β· user | Mapped via SAML groups |
| Stalwart | member | member (mailing list) |
The null Role (Exclusion)
Setting a role to null means the group is completely invisible to that app. The adapter won't create anything β no org, no channel, no group. This is how you control information boundaries:
# HR should NOT see code repositories
ROLE_MAPPING='{"hr": null}' # on the Gitea adapter
# Engineering should NOT see HR timesheets
ROLE_MAPPING='{"engineering": null}' # on the Kimai adapter (if applicable)
Helm Chart: Declarative RBAC
When using the Helm chart, role mappings are defined declaratively in values.yaml:
rbac:
groups:
engineering:
displayName: "Engineering"
roles:
gitea: owner # Full repo access
outline: member # Wiki read/write
mattermost: member # Channel member
seafile: member # File access
kimai: user # Track own time
stalwart: member # Mailing list
management:
displayName: "Management"
roles:
gitea: read # View-only repos
outline: member # Wiki read/write
mattermost: admin # Channel admin
seafile: member # File access
kimai: teamlead # Approve timesheets
hr:
displayName: "Human Resources"
roles:
gitea: null # No code access
outline: member # Wiki read/write
seafile: member # File access
kimai: teamlead # Approve timesheets
stalwart: member # Mailing list
The chart automatically converts this into per-adapter ROLE_MAPPING ConfigMaps.
Pending Membership Queue
If you add a user to a group before they've ever logged into an app, the adapter queues the membership. The next time that user logs in (Authentik fires a user_login webhook), all pending memberships are applied automatically.
Architecture
How all the pieces fit together.
Hub-and-Spoke SSO
All traffic enters through Caddy (reverse proxy) on a single port. Caddy routes by subdomain to the correct backend. Authentication flows through Authentik as the central identity hub.
Browser β http://{app}.{DOMAIN}:{PORT}
β
βββ Caddy (reverse proxy, static IP 172.28.0.250)
βββ auth.* β Authentik (OIDC/SAML provider)
βββ docs.* β Outline (OIDC)
βββ chat.* β Mattermost (OAuth2)
βββ git.* β Gitea (OIDC)
βββ files.* β Seafile (OAuth2)
βββ time.* β Kimai (SAML)
βββ tasks.* β Vikunja (OIDC)
βββ vault.* β Vaultwarden (OIDC)
βββ groupware.* β Stalwart (OIDC)
βββ office.* β OnlyOffice (JWT)
βββ minio.* β MinIO (internal)
Authentication Protocols
| App | Protocol | Notes |
|---|---|---|
| Outline, Gitea, Vikunja, Vaultwarden, Stalwart | OIDC | Standard OpenID Connect |
| Mattermost | OAuth2 | Uses "GitLab" provider type (same protocol) |
| Seafile | OAuth2 | Custom OAuth via patched settings |
| Kimai | SAML 2.0 | Only external auth method Kimai supports |
| OnlyOffice | JWT | No user accounts β shared secret with Seafile |
Blueprints (Zero-Touch Config)
Authentik blueprints are YAML files that declaratively create all SSO providers, webhook transports, and scope mappings. They're mounted into the Authentik worker and auto-applied on startup β no UI clicks needed.
There are 18 blueprints in apps/authentik/blueprints/: one per OIDC/SAML provider, one per webhook transport, plus shared scope and comms launcher configs.
Adapters
Adapters are lightweight FastAPI microservices that receive Authentik webhooks and translate them into app-specific API calls. There are 5 adapters:
| Adapter | Receives | Creates |
|---|---|---|
| outline-adapter | Group CRUD, user login | Outline groups + membership |
| mattermost-adapter | Group CRUD, user login | Channels + membership + slash commands |
| gitea-adapter | Group CRUD, user login | Organizations + team membership |
| seafile-adapter | Group CRUD, user login | Groups + library sharing + membership |
| stalwart-adapter | Group CRUD, user login | Mailing lists + JIT mailbox provisioning |
Init Containers
Init containers run once at first boot to configure apps that can't be fully configured via environment variables alone. All are idempotent β safe to re-run.
| Container | What it does |
|---|---|
| outline-init | Seeds API key, promotes first user to admin |
| mattermost-init | Creates team + bot token + slash commands |
| gitea-init | Registers OIDC auth source + service account |
| kimai-init | Fetches SAML signing cert, generates config |
| stalwart-init | Creates mail domain via REST API |
| minio-init | Creates S3 bucket for Outline |
The Split URL Pattern
A critical implementation detail: browser-facing and server-to-server OIDC URLs must differ inside Docker.
- Browser:
http://auth.localhost(external, via Caddy) - Server:
http://authentik-server:9000(Docker internal hostname)
Apps that support separate browser/server URLs use this split directly. Apps with a single authority URL (Gitea, Vikunja, Vaultwarden) use extra_hosts to point the auth domain to Caddy's static IP.
Adapters & Webhooks
How Authentik events propagate to every app in real time.
What Are Adapters?
Adapters are lightweight Python (FastAPI) microservices that act as translators between Authentik and each app. When something happens in Authentik (a group is created, a user logs in), Authentik fires an HTTP webhook to the relevant adapters, which then call the target app's API.
Authentik event
β
βββwebhookβββ outline-adapter ββAPIβββ Outline
βββwebhookβββ mattermost-adapter ββAPIβββ Mattermost
βββwebhookβββ gitea-adapter ββAPIβββ Gitea
βββwebhookβββ seafile-adapter ββAPIβββ Seafile
βββwebhookβββ stalwart-adapter ββAPIβββ Stalwart
Webhook Events
| Event | Trigger | Adapter Action |
|---|---|---|
model_created (group) | New group created in Authentik | Create org/channel/group/mailing list |
model_updated (group) | Group membership changed | Sync membership to app |
model_deleted (group) | Group deleted | Delete corresponding resource |
user_login | User logs into any app | Apply pending memberships, JIT mailbox |
Pending Queue
Each adapter maintains a SQLite database for pending memberships. If a user is added to a group but hasn't logged into the target app (no local account exists), the membership is queued. On next login, all pending memberships are applied.
Adapter Endpoints
POST /webhook β Receives Authentik events
GET /health β Liveness probe (returns 200)
GET /pending β Lists queued pending operations
Shared Base
All adapters share apps/_shared/adapter_base.py which provides:
- SQLite database initialization and pending queue management
- Webhook payload parsing
load_role_mapping()β parsesROLE_MAPPINGenv varget_group_role()β resolves a group name to its app-specific role (or None for exclusion)
Adding Users & Groups
Step-by-step: create your company's user structure in Authentik.
1 Plan Your Groups
Think about your company's job functions. Each group maps to a set of permissions across all apps. Common examples:
| Group | Who's in it | What they get |
|---|---|---|
engineering | Developers, DevOps | Full code access, wiki read/write, task boards |
management | Team leads, executives | Wiki admin, timesheet approval, view-only code |
hr | HR team | Wiki admin, timesheet approval, NO code access |
design | Designers | File access, wiki read/write, task boards |
2 Create Groups in Authentik
- Open Authentik admin: auth.localhost/if/admin β Directory β Groups
- Click Create
- Enter the group name (e.g.,
engineering) - Click Create
Within seconds, all 5 adapters receive a webhook and create the corresponding resources:
- Gitea: organization
engineering - Mattermost: channel
engineering - Outline: group
engineering - Seafile: group
engineering - Stalwart: mailing list
engineering@yourdomain.com
3 Create Users
- Go to Directory β Users in Authentik admin
- Click Create
- Fill in: username, display name, email
- Set a temporary password (user can change on first login)
- Click Create
4 Add Users to Groups
- Open the user you just created
- Go to the Groups tab
- Click Add to existing group
- Select the group(s) this user belongs to
5 Tell Users to Log In
Share the app URLs with your users. On first login to any app via SSO:
- A local account is auto-created in the app
- All pending group memberships are applied
- If Stalwart is enabled, a mailbox is auto-provisioned
- If Mattermost onboarding is enabled, they get a welcome DM with communication links
Industry Presets
Pre-built RBAC configurations for common company types. Zero to productive in seconds.
What Are Presets?
Klyvra ships with 5 industry presets that define groups, roles, and per-app permissions tailored to specific company types. Instead of configuring RBAC from scratch, pick a preset and apply it.
Available Presets
| Preset | Groups | Best For |
|---|---|---|
software-house | engineering, management, design, qa, devops | Dev shops, agencies, SaaS companies |
marketing-agency | creative, account-management, strategy, production | Agencies, media companies |
law-firm | partners, associates, paralegals, admin-staff | Legal practices, consultancies |
consultancy | consultants, analysts, operations, leadership | Management consulting, advisory |
non-profit | program-staff, volunteers, admin, board | NGOs, foundations, charities |
Applying a Preset (Docker Compose)
# Generate ROLE_MAPPING env vars for your .env file
python3 scripts/apply_preset.py presets/software-house.yaml --env-format >> .env
# Restart adapters to pick up new role mappings
make restart
Applying a Preset (Helm)
# Generate Helm values override
python3 scripts/apply_preset.py presets/software-house.yaml --helm-format > my-rbac.yaml
# Install or upgrade with the preset
helm install klyvra ./chart/klyvra -f my-rbac.yaml --set global.domain=company.com
ROLE_MAPPING values or values.yaml to match your exact needs.
Custom Domain & TLS
Move from localhost to a real domain with automatic HTTPS.
Setting a Custom Domain
make destroy # Clean slate (if upgrading from localhost)
make bootstrap DOMAIN=company.com TLS_EMAIL=admin@company.com
make up
All services become https://auth.company.com, https://docs.company.com, https://chat.company.com, etc.
make bootstrap detects a non-localhost domain, it creates a Caddyfile.prod symlink with bare hostnames (auto-HTTPS). For local development, make bootstrap (no DOMAIN) creates a Caddyfile.dev symlink with HTTP-only routing. No manual Caddyfile editing is needed.
DNS Setup
Point a wildcard DNS record to your server:
*.company.com A β your-server-ip
Or create individual A records for each subdomain: auth, docs, chat, git, files, time, tasks, vault, groupware, office, mail, calendar, contacts.
TLS (HTTPS)
TLS is fully automated when using a custom domain:
- Run
make bootstrap DOMAIN=company.com TLS_EMAIL=admin@company.com - Point DNS wildcard
*.company.comto your server - Run
make upβ Caddy auto-provisions Let's Encrypt certificates for all subdomains
Caddy handles certificate issuance, renewal, and HTTPS redirects automatically. No manual configuration needed.
Switching Between Dev and Production
The bootstrap command manages this automatically:
| Command | Mode | URLs |
|---|---|---|
make bootstrap | Dev (Caddyfile.dev) | http://app.localhost |
make bootstrap DOMAIN=x TLS_EMAIL=y | Prod (Caddyfile.prod) | https://app.company.com |
Deploying with Helm
Deploy Klyvra on Kubernetes with the Helm chart.
Prerequisites
- Kubernetes cluster (1.25+)
- Helm 3
- An ingress controller (nginx-ingress, Traefik, etc.)
- cert-manager (for automatic TLS) β optional but recommended
Build & Push Images
Klyvra uses 10 custom images (adapters, init jobs, custom Stalwart/Seafile). Build and push them to your registry:
# Build locally
make images
# Push to your registry
REGISTRY=ghcr.io/your-org TAG=v0.1.0 make push-images
Install
helm install klyvra ./chart/klyvra \
--set global.domain=company.com \
--set global.imageRegistry=ghcr.io/your-org \
--set global.imageTag=v0.1.0
Configure RBAC in values.yaml
The chart's values.yaml has a dedicated RBAC section. Define your groups and their per-app roles:
rbac:
groups:
engineering:
displayName: "Engineering"
roles:
gitea: owner
outline: member
mattermost: member
seafile: member
kimai: user
stalwart: member
management:
displayName: "Management"
roles:
gitea: read
outline: member
mattermost: admin
seafile: member
kimai: teamlead
hr:
displayName: "Human Resources"
roles:
gitea: null # Excluded from code repos
outline: member
seafile: member
kimai: teamlead
The chart converts this into per-adapter ConfigMaps automatically β no manual JSON wrangling.
Enable/Disable Apps
# values.yaml
outline:
enabled: true
kimai:
enabled: false # Don't need time tracking? Disable it.
Ingress
The chart creates Ingress resources for all 11 subdomains. Configure your ingress class:
global:
ingress:
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
helm install, the same first-login process applies: visit the Authentik URL to create the admin account, then start creating users and groups.
App Reference
All 10 apps at a glance β what they do, how they authenticate, and what they replace.
| App | Subdomain | Auth | Replaces | Group Sync |
|---|---|---|---|---|
| Authentik | auth.* | Hub | Okta, Azure AD | β |
| Outline | docs.* | OIDC | Notion, Confluence | β Adapter |
| Mattermost | chat.* | OAuth2 | Slack, Teams | β Adapter |
| Gitea | git.* | OIDC | GitHub, GitLab | β Adapter |
| Seafile | files.* | OAuth2 | Google Drive, OneDrive | β Adapter |
| Kimai | time.* | SAML | Toggl, Harvest | SAML groups |
| Vikunja | tasks.* | OIDC | Asana, Trello | SSO only |
| Vaultwarden | vault.* | OIDC | 1Password, LastPass | SSO only |
| OnlyOffice | office.* | JWT | Google Docs, MS Office | No accounts |
| Stalwart | groupware.* | OIDC | Gmail, Exchange | β Adapter |
| MinIO | minio.* | Internal | AWS S3 | No accounts |
Notes
- OnlyOffice has no user accounts β it's a document server accessed via Seafile's integration for editing Word/Excel/PowerPoint files. Authenticated via shared JWT secret with Seafile.
- MinIO is the S3 backend for Outline's file storage. Not a user-facing app. Uses static admin credentials.
- Vaultwarden uses the OIDCWarden fork β the official image doesn't support SSO.
- Mattermost uses Enterprise Edition (free tier, no license needed) because Team Edition removed OAuth in v9.x.
- Kimai maps SAML group claims to roles:
kimai-adminβ SUPER_ADMIN,kimai-teamleadβ TEAMLEAD. - Kimai matches group names literally β the Authentik group must be named exactly
kimai-adminorkimai-teamlead(notengineeringormanagement). This is a Kimai limitation.
Environment Variables
All config lives in .env, generated by make bootstrap.
User-Configurable
| Variable | Default | Description |
|---|---|---|
DOMAIN | localhost | Base domain β all subdomains derived from this |
HTTP_PORT | 80 | Port Caddy listens on |
MATTERMOST_TEAM_NAME | main | Default Mattermost team name |
ENABLE_COMMS_COMMANDS | true | Create /comms, /mail, /calendar slash commands |
ENABLE_COMMS_ONBOARDING | true | Send welcome DM with comms links on first login |
MAIL_URL | http://groupware.${DOMAIN} | Target URL for mail redirect |
CALENDAR_URL | http://groupware.${DOMAIN} | Target URL for calendar redirect |
CONTACTS_URL | http://groupware.${DOMAIN} | Target URL for contacts redirect |
Auto-Generated (don't touch)
make bootstrap generates 30+ secrets using openssl rand. These include:
- Database passwords for all 7 databases
- OIDC client secrets for all 7 OIDC providers
- Authentik secret key, bootstrap token
- MinIO root password
- OnlyOffice JWT secret
- Stalwart admin password
make bootstrap, it regenerates all passwords. Existing database volumes still have the old passwords. Run make destroy first to wipe volumes.
Troubleshooting
Common issues and how to fix them.
Port 80 in use
make bootstrap HTTP_PORT=8080
make up
Stale volumes after re-bootstrapping
If you re-run make bootstrap (which regenerates passwords), existing database volumes still have the old passwords:
make destroy # removes containers AND volumes
make bootstrap # fresh .env
make up # clean start
*.localhost doesn't resolve
Most modern systems resolve *.localhost to 127.0.0.1 (RFC 6761). If your browser can't reach the URLs, add entries to /etc/hosts:
# /etc/hosts
127.0.0.1 auth.localhost docs.localhost chat.localhost git.localhost
127.0.0.1 files.localhost time.localhost tasks.localhost vault.localhost
127.0.0.1 groupware.localhost office.localhost minio.localhost
Service won't start
docker compose logs <service-name> # Check logs
docker compose ps -a # Check exit codes
make logs SVC=<service-name> # Follow logs
SSO button missing in Seafile
Seafile's OAuth config is patched at runtime by an embedded boot script. If the SSO button is missing:
docker logs klyvra-seafile-1 2>&1 | grep oauth-boot
Vaultwarden SSO not working
Klyvra uses OIDCWarden (timshel/oidcwarden:latest), not the official Vaultwarden image. The official image silently ignores SSO_* env vars. OIDCWarden still requires a master password after SSO login (by design β it encrypts your vault).
Mattermost shows "GitLab" as SSO provider
This is a legacy label. If your Mattermost still shows "GitLab" instead of "Login with Klyvra", check that you're using the latest Klyvra compose config. Mattermost's free tier only supports "GitLab" as an OAuth2 provider type β Klyvra patches the button text to say "Login with Klyvra" but the internal provider type remains "gitlab".
Init container keeps restarting
Init containers use restart: on-failure and retry until their target app is ready. Check if the target app is healthy:
docker compose ps # Look for unhealthy services
make status # Full status dashboard
Checking adapter health
# View adapter logs
make logs SVC=outline-adapter
make logs SVC=gitea-adapter
# Check pending queue
docker compose exec outline-adapter curl -s localhost:8000/pending
Production Deployment
Deploy Klyvra in production using Docker Compose or Kubernetes.
Hardware Requirements
| Minimum | Recommended | |
|---|---|---|
| RAM | 8 GB | 16 GB |
| CPU | 2 vCPUs | 4 vCPUs |
| Storage | 40 GB SSD | 100 GB+ SSD |
Software Requirements
- Docker Compose: Docker Engine 24.0+ with Compose v2
- Kubernetes: Version 1.25+ (if using Helm)
- DNS: A wildcard A record (
*.company.com) pointing to your server makeandopensslinstalled on the deployment machine
Docker Compose Deployment
1 Bootstrap with production settings
make bootstrap DOMAIN=company.com TLS_EMAIL=admin@company.com
This generates 35+ random secrets, sets the base domain for all subdomains, configures Caddy for automatic HTTPS via Let's Encrypt, and symlinks the production Caddyfile.
2 Start services
make up
3 Configure firewall
| Port | Protocol | Service |
|---|---|---|
| 80 | TCP | HTTP (Caddy redirect) |
| 443 | TCP | HTTPS (Caddy reverse proxy) |
| 25 | TCP | SMTP (Stalwart) |
| 587 | TCP | SMTP Submission (Stalwart) |
| 143 | TCP | IMAP (Stalwart) |
| 2222 | TCP | SSH (Gitea git clone) |
Email DNS Configuration
For production email deliverability, configure these DNS records (replace company.com with your domain):
Required records
| Type | Host | Value | Purpose |
|---|---|---|---|
| MX | company.com | 10 mail.company.com | Route inbound email |
| A | mail.company.com | <your-server-ip> | Mail server hostname |
| TXT | company.com | v=spf1 mx ~all | SPF: authorize server to send mail |
DKIM setup
- Access Stalwart admin at
https://groupware.company.com/admin/(useSTALWART_ADMIN_PASSWORDfrom.env) - Navigate to Settings β DKIM β Stalwart auto-generates DKIM keys on domain creation
- Copy the public key and create a DNS TXT record at
default._domainkey.company.com
DMARC
Type: TXT
Host: _dmarc.company.com
Value: v=DMARC1; p=quarantine; rua=mailto:admin@company.com; pct=100
Start with p=none (monitor only) before switching to p=quarantine or p=reject.
Reverse DNS (PTR)
Contact your hosting provider to set the PTR record for your server's IP to mail.company.com. Many email providers reject mail from IPs without matching PTR records.
Mail client autoconfiguration
Klyvra serves autoconfig/autodiscover XML files automatically. Point these subdomains to your server:
autoconfig.company.com(Thunderbird)autodiscover.company.com(Outlook/iOS)
Backup & Restore
# Back up all databases, volumes, and .env
make backup
# Restore from a specific backup
make restore BACKUP=backups/klyvra-YYYYMMDD-HHMMSS
Run backups daily and copy the archive to offsite storage.
Upgrading
# Docker Compose: pull latest images, rebuild, restart
make upgrade
# Kubernetes
helm upgrade klyvra ./chart/klyvra
make backup before make upgrade. If something breaks, restore with make restore BACKUP=path.
Security Hardening
Klyvra's Caddy reverse proxy injects security headers on all responses automatically:
- Strict-Transport-Security (HSTS) β forces HTTPS for 1 year (production mode)
- X-Content-Type-Options β prevents MIME-type sniffing
- X-Frame-Options β restricts iframe embedding to same-origin
- Referrer-Policy β limits referrer information to external sites
- Permissions-Policy β disables camera, microphone, geolocation
Additional measures
- Restrict Authentik admin access to specific IPs via Caddy/Ingress rules
- Keep management ports (MinIO console 9001) internal or behind VPN
- On Kubernetes, enable
global.networkPolicies.enabledfor pod-to-pod isolation
Monitoring
make status # Container status dashboard
make healthcheck # HTTP check on all 13 routes
make smoke-test # Automated E2E verification
make logs SVC=x # Follow logs for a specific service
Test email deliverability with mail-tester.com or mxtoolbox.com. Aim for 8/10 or higher.
Communications Front Door
Unified mail, calendar, and contacts access via Mattermost and dedicated subdomains.
What It Is
Klyvra provides a "communications front door" β dedicated subdomains and Mattermost slash commands that give users quick access to email, calendar, and contacts without needing to remember separate URLs.
Subdomain Routes
| URL | Default Target | Env Variable |
|---|---|---|
http://mail.{DOMAIN} | groupware.{DOMAIN} | MAIL_URL |
http://calendar.{DOMAIN} | groupware.{DOMAIN} | CALENDAR_URL |
http://contacts.{DOMAIN} | groupware.{DOMAIN} | CONTACTS_URL |
By default, these routes point to Stalwart's web UI. Set MAIL_URL, CALENDAR_URL, and CONTACTS_URL to redirect to an external groupware backend (e.g., Roundcube, SOGo).
Mattermost Slash Commands
When ENABLE_COMMS_COMMANDS=true (default), Mattermost init creates these slash commands:
| Command | Action |
|---|---|
/comms mail | Opens the mail URL |
/comms calendar | Opens the calendar URL |
/comms contacts | Opens the contacts URL |
/mail | Shortcut β opens mail directly |
/calendar | Shortcut β opens calendar directly |
/contacts | Shortcut β opens contacts directly |
Onboarding DM
When ENABLE_COMMS_ONBOARDING=true (default), new users receive a one-time direct message in Mattermost after their first Authentik login. The DM contains links to mail, calendar, and contacts.
Configuration
| Variable | Default | Description |
|---|---|---|
ENABLE_COMMS_COMMANDS | true | Create slash commands on bootstrap |
ENABLE_COMMS_ONBOARDING | true | Send welcome DM with comms links |
MAIL_URL | http://groupware.${DOMAIN} | Target for mail route/command |
CALENDAR_URL | http://groupware.${DOMAIN} | Target for calendar route/command |
CONTACTS_URL | http://groupware.${DOMAIN} | Target for contacts route/command |
Makefile Commands
Every command available for managing your Klyvra deployment.
| Command | Description |
|---|---|
make bootstrap | Generate .env with random secrets |
make bootstrap DOMAIN=x HTTP_PORT=y | Custom domain and port |
make up | Build images, start all services, wait for health |
make down | Stop all services (data preserved in volumes) |
make destroy | Stop and delete all data (volumes removed) |
make start / make stop | Start/stop without rebuilding |
make healthcheck | Quick HTTP check on all routes |
make test | Run 149 adapter unit tests |
make verify | Run full verification suite (98 checks) |
make logs [SVC=x] | Follow container logs (all or specific) |
make restart [SVC=x] | Restart services (all or specific) |
make status | Container status dashboard |
make images | Build all 10 custom Docker images |
make push-images | Build and push images to registry |
make upgrade | Pull latest images, rebuild, and restart |
make backup | Back up all databases, volumes, and .env |
make restore BACKUP=path | Restore from a backup directory |
make smoke-test | E2E endpoint and OIDC validation (requires running stack) |