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 33 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
Stalwartmail.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 Create the Authentik Admin

Open http://auth.localhost/if/flow/initial-setup/ in your browser.

You'll see a one-time setup form. Create your admin account with a strong password. This is the only manual step in the entire setup.

⚠️ Save these credentials This is the Authentik admin account. If you lose it, you'll need to reset via the database. Consider storing it in Vaultwarden once it's running!

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 (done by blueprints)
  • 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 OIDC"
  • Mattermost β†’ "GitLab" (it's actually Authentik β€” Mattermost calls all OAuth2 "GitLab")
  • Gitea β†’ "Sign in with Authentik"
  • Seafile β†’ "Single Sign-On"
  • Kimai β†’ "Login with Authentik"
  • Vikunja β†’ "Authentik"
  • 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.

4 Stalwart Mail

Stalwart is slightly different β€” mailboxes are created automatically on first SSO login (JIT provisioning). After logging in at mail.localhost, your mailbox is ready for IMAP/SMTP at:

  • IMAP: mail.localhost:143
  • SMTP: mail.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 4 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)
    • 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
Outlineadmin Β· member Β· viewermember
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
        kimai: user            # Track own time
        stalwart: member       # Mailing list

    management:
      displayName: "Management"
      roles:
        gitea: read            # View-only repos
        outline: admin         # Wiki admin
        mattermost: admin      # Channel admin
        kimai: teamlead        # Approve timesheets

    hr:
      displayName: "Human Resources"
      roles:
        gitea: null            # No code access
        outline: admin         # Wiki admin
        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)
                 β”œβ”€β”€ mail.*      β†’ 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 Outline

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

AdapterReceivesCreates
outline-adapterGroup CRUD, user loginOutline groups + membership
mattermost-adapterGroup CRUD, user loginChannels + membership + slash commands
gitea-adapterGroup CRUD, user loginOrganizations + team 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──→ 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 4 adapters receive a webhook and create the corresponding resources:

  • Gitea: organization engineering
  • Mattermost: channel engineering
  • Outline: 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

Custom Domain & TLS

Move from localhost to a real domain with automatic HTTPS.

Setting a Custom Domain

make destroy                            # Clean slate
make bootstrap DOMAIN=company.com       # Generate .env with your domain
make up                                 # Start everything

All services become auth.company.com, docs.company.com, chat.company.com, etc.

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, mail, office, minio.

Enabling TLS (HTTPS)

Caddy supports automatic Let's Encrypt certificates. To enable:

1 Edit the Caddyfile

Open apps/caddy/Caddyfile and remove the auto_https off line. Change the protocol from http:// to bare domain matching:

# Before:
http://auth.{$DOMAIN}:{$HTTP_PORT} { ... }

# After (Caddy auto-enables HTTPS):
auth.{$DOMAIN} { ... }

2 Update ports

In compose.yaml, expose ports 80 and 443 on Caddy for HTTP→HTTPS redirect and TLS respectively.

3 Restart

make down && make up

Caddy automatically obtains and renews Let's Encrypt certificates for all subdomains.

⚠️ 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.

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
        kimai: user
        stalwart: member

    management:
      displayName: "Management"
      roles:
        gitea: read
        outline: admin
        mattermost: admin
        kimai: teamlead

    hr:
      displayName: "Human Resources"
      roles:
        gitea: null            # Excluded from code repos
        outline: admin
        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, OneDriveSSO only
Kimaitime.*SAMLToggl, HarvestSAML groups
Vikunjatasks.*OIDCAsana, TrelloSSO only
Vaultwardenvault.*OIDC1Password, LastPassSSO only
OnlyOfficeoffice.*JWTGoogle Docs, MS OfficeNo accounts
Stalwartmail.*OIDCGmail, Exchangeβœ“ Adapter
MinIOminio.*InternalAWS S3No accounts

Notes

  • OnlyOffice has no user accounts β€” it's embedded in Outline for document editing (Word/Excel/PowerPoint). Authenticated via shared JWT secret.
  • 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.

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://mail.${DOMAIN}Target URL for mail redirect
CALENDAR_URLhttp://calendar.${DOMAIN}Target URL for calendar redirect
CONTACTS_URLhttp://contacts.${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 mail.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 expected. Mattermost's free tier only supports "GitLab" as an OAuth2 provider type. Authentik is configured to match GitLab's userinfo format, so it works transparently. The button says "GitLab" but actually redirects to Authentik.

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

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 95 adapter unit tests
make verifyRun full verification suite (74 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