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 11 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 36 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 |
| OnlyOffice | office.localhost | Document editing |
| Paperless | dms.localhost | Document management (OCR) |
| Whiteboard | draw.localhost | Excalidraw, gated by SSO |
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 11 apps are pre-configured
- Providers β 8 OIDC/OAuth + 1 SAML + 1 proxy 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 β auto-redirects straight through Klyvra SSO
- 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)
- Paperless β "Klyvra"
- Whiteboard β no button β forward-auth redirects unauthenticated visitors to Authentik automatically
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 Documents & Whiteboard
Two apps behave a little differently:
- Paperless (dms.localhost) β click "Klyvra" to sign in; your account is created on first login. A local
adminaccount also exists (password in.envasPAPERLESS_ADMIN_PASSWORD) for superuser tasks. - Whiteboard (draw.localhost) β there is no login page at all. Caddy checks your Authentik session before proxying; visitors without one are redirected to log in first.
MAIL_URL, CALENDAR_URL, and CONTACTS_URL in .env to your hosted provider β the mail.*/calendar.*/contacts.* routes and Mattermost slash commands will link straight to it.
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 4 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)
- 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 |
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
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
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.
βΉοΈ No auto-removal
Removing a user from an Authentik group does NOT remove them from the corresponding app. This is intentional β auto-removal is destructive and could cause data loss. Manage removals directly in each app when needed.
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)
βββ office.* β OnlyOffice (JWT)
βββ dms.* β Paperless (OIDC)
βββ draw.* β Excalidraw (forward-auth)
βββ minio.* β MinIO (internal)
Authentication Protocols
| App | Protocol | Notes |
|---|---|---|
| Outline, Gitea, Vikunja, Vaultwarden, Paperless | OIDC | Standard OpenID Connect |
| Whiteboard (Excalidraw) | Forward-auth | Caddy asks Authentik's embedded outpost before proxying |
| 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 4 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 |
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 |
| 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 Events
| Event | Trigger | Adapter Action |
|---|---|---|
model_created (group) | New group created in Authentik | Create org/channel/group |
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, join default team |
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 4 adapters receive a webhook and create the corresponding resources:
- Gitea: organization
engineering - Mattermost: channel
engineering - Outline: group
engineering - Seafile: group
engineering
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 (Mattermost also joins them to the default team)
- 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, office, dms, draw, 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 builds custom images for the adapters, init jobs, and Seafile (embedded OAuth boot). 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
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 every enabled app's subdomain. 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 11 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 |
| Paperless-ngx | dms.* | OIDC | DocuWare, paper archives | SSO only |
| Excalidraw | draw.* | Forward-auth | Miro, FigJam | Any authenticated user |
| 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 | (empty) | Your hosted mail provider β front-door page explains setup until set |
CALENDAR_URL | (empty) | Your hosted calendar provider β front-door page explains setup until set |
CONTACTS_URL | (empty) | Your hosted contacts provider β front-door page explains setup until set |
Auto-Generated (don't touch)
make bootstrap generates 30+ secrets using openssl rand. These include:
- Database passwords for all 8 databases
- OIDC client secrets for all 7 OIDC providers
- Authentik secret key, bootstrap token
- MinIO root password
- OnlyOffice JWT secret
- Paperless admin password & secret key
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 dms.localhost draw.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) |
| 2222 | TCP | SSH (Gitea git clone) |
Email (Bring Your Own Provider)
Klyvra does not self-host email β port 25 blocking, IP reputation, and SPF/DKIM/PTR coordination make it the one service that can't ship turnkey. Instead:
- Choose a hosted provider (EU providers work well for data-sovereignty requirements)
- Set
MAIL_URL,CALENDAR_URL, andCONTACTS_URLin.envto its web endpoints mail.company.com,calendar.company.com, andcontacts.company.comredirect there, and Mattermost's/mail,/calendar,/contactscommands link users directly
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 14 routes
make smoke-test # Automated E2E verification
make logs SVC=x # Follow logs for a specific service
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} | setup page until configured | MAIL_URL |
http://calendar.{DOMAIN} | setup page until configured | CALENDAR_URL |
http://contacts.{DOMAIN} | setup page until configured | CONTACTS_URL |
Until configured, these routes serve a small page explaining how to set them up. Set MAIL_URL, CALENDAR_URL, and CONTACTS_URL to your hosted provider and the routes redirect there directly.
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 | (empty) | Your hosted mail provider |
CALENDAR_URL | (empty) | Your hosted calendar provider |
CONTACTS_URL | (empty) | Your hosted contacts provider |
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) |