Quick 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)
  • openssl and make (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.

πŸ’‘ Custom port? If port 80 is taken, run 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:

AppURLWhat it does
Authentikauth.localhostIdentity & SSO admin
Outlinedocs.localhostWiki & knowledge base
Mattermostchat.localhostTeam chat & channels
Giteagit.localhostGit hosting & CI/CD
Seafilefiles.localhostFile sync & share
Kimaitime.localhostTime tracking
Vikunjatasks.localhostTask management
Vaultwardenvault.localhostPassword manager
Stalwartgroupware.localhostEmail, calendar, contacts
OnlyOfficeoffice.localhostDocument editing
ℹ️ Next step Head to the Your First Login guide to create your admin account and start using the suite.

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_PASSWORD in your .env file

To find the password:

grep AUTHENTIK_BOOTSTRAP_PASSWORD .env
πŸ’‘ Tip Store this password in Vaultwarden once it's running. You can change it later in the Authentik admin panel under Directory β†’ Users β†’ akadmin.

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.

πŸ’‘ Auto-provisioning You don't need to create accounts in each app. The first time you log in via SSO, a local account is automatically created in that app with your Authentik email and display name.
⚠️ Outline first-login gotcha The very first user to log into Outline automatically becomes the workspace admin. Make sure the person who sets up the stack logs into Outline first β€” not a regular user. If the wrong person logs in first, you'll need to manually adjust admin status via the Outline API.

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:

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:

  1. You create a group in Authentik (e.g., "engineering")
  2. Authentik fires a webhook to all 5 adapters
  3. 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
  4. When you add a user to the group, adapters sync membership
  5. 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}'
ValueEffect
"owner", "admin", etc.App-specific role name β€” see table below
nullExclude β€” this group is NOT synced to this app at all
(group not listed)Default role for the app (usually member)

Available Roles Per App

AppRolesDefault
Giteaowner Β· admin Β· write Β· readowner
Mattermostadmin Β· membermember
Outlinemembermember
Seafilemembermember
Kimaisuper_admin Β· teamlead Β· userMapped via SAML groups
Stalwartmembermember (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.

ℹ️ 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)
                  β”œβ”€β”€ groupware.*  β†’ Stalwart     (OIDC)
                 β”œβ”€β”€ office.*    β†’ OnlyOffice   (JWT)
                 └── minio.*     β†’ MinIO        (internal)

Authentication Protocols

AppProtocolNotes
Outline, Gitea, Vikunja, Vaultwarden, StalwartOIDCStandard OpenID Connect
MattermostOAuth2Uses "GitLab" provider type (same protocol)
SeafileOAuth2Custom OAuth via patched settings
KimaiSAML 2.0Only external auth method Kimai supports
OnlyOfficeJWTNo 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:

AdapterReceivesCreates
outline-adapterGroup CRUD, user loginOutline groups + membership
mattermost-adapterGroup CRUD, user loginChannels + membership + slash commands
gitea-adapterGroup CRUD, user loginOrganizations + team membership
seafile-adapterGroup CRUD, user loginGroups + library sharing + membership
stalwart-adapterGroup CRUD, user loginMailing 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.

ContainerWhat it does
outline-initSeeds API key, promotes first user to admin
mattermost-initCreates team + bot token + slash commands
gitea-initRegisters OIDC auth source + service account
kimai-initFetches SAML signing cert, generates config
stalwart-initCreates mail domain via REST API
minio-initCreates 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

EventTriggerAdapter Action
model_created (group)New group created in AuthentikCreate org/channel/group/mailing list
model_updated (group)Group membership changedSync membership to app
model_deleted (group)Group deletedDelete corresponding resource
user_loginUser logs into any appApply 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() β€” parses ROLE_MAPPING env var
  • get_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:

GroupWho's in itWhat they get
engineeringDevelopers, DevOpsFull code access, wiki read/write, task boards
managementTeam leads, executivesWiki admin, timesheet approval, view-only code
hrHR teamWiki admin, timesheet approval, NO code access
designDesignersFile access, wiki read/write, task boards

2 Create Groups in Authentik

  1. Open Authentik admin: auth.localhost/if/admin β†’ Directory β†’ Groups
  2. Click Create
  3. Enter the group name (e.g., engineering)
  4. 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

  1. Go to Directory β†’ Users in Authentik admin
  2. Click Create
  3. Fill in: username, display name, email
  4. Set a temporary password (user can change on first login)
  5. Click Create

4 Add Users to Groups

  1. Open the user you just created
  2. Go to the Groups tab
  3. Click Add to existing group
  4. Select the group(s) this user belongs to
πŸ’‘ What happens next Authentik fires a webhook. If the user has already logged into an app, they're immediately added to the group/channel/org. If they haven't logged in yet, the membership is queued and applied automatically on their first login.

5 Tell Users to Log In

Share the app URLs with your users. On first login to any app via SSO:

  1. A local account is auto-created in the app
  2. All pending group memberships are applied
  3. If Stalwart is enabled, a mailbox is auto-provisioned
  4. 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

PresetGroupsBest For
software-houseengineering, management, design, qa, devopsDev shops, agencies, SaaS companies
marketing-agencycreative, account-management, strategy, productionAgencies, media companies
law-firmpartners, associates, paralegals, admin-staffLegal practices, consultancies
consultancyconsultants, analysts, operations, leadershipManagement consulting, advisory
non-profitprogram-staff, volunteers, admin, boardNGOs, 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
πŸ’‘ Customise after applying Presets are starting points, not straitjackets. After applying, edit the generated 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.

πŸ’‘ What happens automatically When 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:

  1. Run make bootstrap DOMAIN=company.com TLS_EMAIL=admin@company.com
  2. Point DNS wildcard *.company.com to your server
  3. 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.

⚠️ Firewall Ports 80 and 443 must be reachable from the internet for Let's Encrypt HTTP-01 challenges. If behind a firewall, consider using DNS-01 challenges instead (requires Caddy DNS plugin configuration).

Switching Between Dev and Production

The bootstrap command manages this automatically:

CommandModeURLs
make bootstrapDev (Caddyfile.dev)http://app.localhost
make bootstrap DOMAIN=x TLS_EMAIL=yProd (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
ℹ️ First boot After 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.

AppSubdomainAuthReplacesGroup Sync
Authentikauth.*HubOkta, Azure ADβ€”
Outlinedocs.*OIDCNotion, Confluenceβœ“ Adapter
Mattermostchat.*OAuth2Slack, Teamsβœ“ Adapter
Giteagit.*OIDCGitHub, GitLabβœ“ Adapter
Seafilefiles.*OAuth2Google Drive, OneDriveβœ“ Adapter
Kimaitime.*SAMLToggl, HarvestSAML groups
Vikunjatasks.*OIDCAsana, TrelloSSO only
Vaultwardenvault.*OIDC1Password, LastPassSSO only
OnlyOfficeoffice.*JWTGoogle Docs, MS OfficeNo accounts
Stalwartgroupware.*OIDCGmail, Exchangeβœ“ Adapter
MinIOminio.*InternalAWS S3No 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-admin or kimai-teamlead (not engineering or management). This is a Kimai limitation.

Environment Variables

All config lives in .env, generated by make bootstrap.

User-Configurable

VariableDefaultDescription
DOMAINlocalhostBase domain β€” all subdomains derived from this
HTTP_PORT80Port Caddy listens on
MATTERMOST_TEAM_NAMEmainDefault Mattermost team name
ENABLE_COMMS_COMMANDStrueCreate /comms, /mail, /calendar slash commands
ENABLE_COMMS_ONBOARDINGtrueSend welcome DM with comms links on first login
MAIL_URLhttp://groupware.${DOMAIN}Target URL for mail redirect
CALENDAR_URLhttp://groupware.${DOMAIN}Target URL for calendar redirect
CONTACTS_URLhttp://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
⚠️ Don't re-bootstrap with existing data If you re-run 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

MinimumRecommended
RAM8 GB16 GB
CPU2 vCPUs4 vCPUs
Storage40 GB SSD100 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
  • make and openssl installed 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

PortProtocolService
80TCPHTTP (Caddy redirect)
443TCPHTTPS (Caddy reverse proxy)
25TCPSMTP (Stalwart)
587TCPSMTP Submission (Stalwart)
143TCPIMAP (Stalwart)
2222TCPSSH (Gitea git clone)

Email DNS Configuration

For production email deliverability, configure these DNS records (replace company.com with your domain):

Required records

TypeHostValuePurpose
MXcompany.com10 mail.company.comRoute inbound email
Amail.company.com<your-server-ip>Mail server hostname
TXTcompany.comv=spf1 mx ~allSPF: authorize server to send mail

DKIM setup

  1. Access Stalwart admin at https://groupware.company.com/admin/ (use STALWART_ADMIN_PASSWORD from .env)
  2. Navigate to Settings β†’ DKIM β€” Stalwart auto-generates DKIM keys on domain creation
  3. 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
⚠️ Always back up before upgrading Run 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.enabled for 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

URLDefault TargetEnv 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:

CommandAction
/comms mailOpens the mail URL
/comms calendarOpens the calendar URL
/comms contactsOpens the contacts URL
/mailShortcut β€” opens mail directly
/calendarShortcut β€” opens calendar directly
/contactsShortcut β€” 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

VariableDefaultDescription
ENABLE_COMMS_COMMANDStrueCreate slash commands on bootstrap
ENABLE_COMMS_ONBOARDINGtrueSend welcome DM with comms links
MAIL_URLhttp://groupware.${DOMAIN}Target for mail route/command
CALENDAR_URLhttp://groupware.${DOMAIN}Target for calendar route/command
CONTACTS_URLhttp://groupware.${DOMAIN}Target for contacts route/command
πŸ’‘ Custom groupware backend If you use an external webmail client like Roundcube or SOGo, set the URL variables to point there. The slash commands and subdomain routes will redirect accordingly.

Makefile Commands

Every command available for managing your Klyvra deployment.

CommandDescription
make bootstrapGenerate .env with random secrets
make bootstrap DOMAIN=x HTTP_PORT=yCustom domain and port
make upBuild images, start all services, wait for health
make downStop all services (data preserved in volumes)
make destroyStop and delete all data (volumes removed)
make start / make stopStart/stop without rebuilding
make healthcheckQuick HTTP check on all routes
make testRun 149 adapter unit tests
make verifyRun 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 statusContainer status dashboard
make imagesBuild all 10 custom Docker images
make push-imagesBuild and push images to registry
make upgradePull latest images, rebuild, and restart
make backupBack up all databases, volumes, and .env
make restore BACKUP=pathRestore from a backup directory
make smoke-testE2E endpoint and OIDC validation (requires running stack)