Customizing a Deployment¶
Klangk is deployed as a Docker container (the "host container") that packages the backend, nginx, Flutter web UI, and workspace image into a single image. The customize/ directory in the Klangk repo is an example of how to build a custom version of this deployment container with plugins, custom CA certificates, and OIDC login hooks baked in. It is meant to be copied out of the repo and adapted to your own needs — edit the scripts, swap out the plugins, add your own login hooks, replace the placeholder logo, etc. Nothing in customize/ is required to run Klangk; it's a starting point for organizations that want a tailored deployment.
What Gets Customized¶
This example custom build layers your changes on top of the standard Klangk host image:
- Plugins — TypeScript extensions and Dart UI plugins are compiled into the workspace image and Flutter web frontend
- CA certificates — Private CA certs are installed into the system trust store of both the host and workspace images
- Login hooks — Custom Python hooks for OIDC login validation (e.g., require invitation)
- Logo — A placeholder
logo.pngdemonstrates branding customization
Prerequisites¶
Directory Layout¶
customize/
build.sh # Main build script (run this)
build-inner.sh # Devenv-side build (called by build.sh)
Dockerfile # Custom image layer on top of klangk-host
docker-compose.yml # Example runtime configuration
plugins.yaml # Plugin list for the build
login_hook.py # Example OIDC login hook
logo.png # Placeholder logo (replace with your own)
ssl/ # Place .pem/.crt CA certs here (gitignored)
data/ # Persistent data directory (gitignored)
Build¶
cd customize
./build.sh
# Or pin to a specific Klangk host container release:
KLANGK_REF=v2026.06.10-1 ./build.sh
The resulting image is tagged ghcr.io/mcdonc/klangk/klangk-host-custom:latest by default. Override with KLANGK_HOST_IMAGE.
How the Build Works¶
The build is split into two scripts: build.sh handles orchestration (git, Docker), and build-inner.sh runs inside the devenv shell where Flutter, podman, and Python are available.
build.sh (outer script):
- Clones (or updates) the Klangk repo at
KLANGK_REFinto.klangk/ - Copies
plugins.yamlinto a staging directory (.plugins/) - Enters the devenv shell and runs
build-inner.sh - Builds the standard
klangk-hostimage from source viascripts/build-host-image.sh - Copies the rebuilt Flutter web output into the Docker build context
- Runs
docker buildwith theDockerfile, which layers custom CA certs, the login hook, the rebuilt web frontend, and the rebuilt workspace image tarball on top ofklangk-host:latest - Cleans up temporary build artifacts
build-inner.sh (runs inside devenv shell):
- Fetches plugins listed in
plugins.yamlviaupdate_plugins.py - Rebuilds the Flutter web frontend with Dart plugin UI via
flutterbuildweb.sh - Rebuilds the workspace container image with plugin extensions and tools via
build-workspace-image.sh - If custom CA certs are present in
ssl/, builds a temporary Dockerfile that layers them onto the workspace image (installs into/usr/local/share/ca-certificates/and runsupdate-ca-certificates) - Exports the final workspace image as
workspace.tarviapodman save
Dockerfile (custom image layer):
Extends klangk-host:latest and replaces four things:
- Installs CA certs from
ssl/into the host's system trust store - Copies
login_hook.pyinto the backend's Python path - Replaces the Flutter web build with the plugin-enabled version
- Replaces the workspace image tarball with the plugin-enabled version
Build Options¶
| Variable | Default | Description |
|---|---|---|
KLANGK_REF |
main |
Klangk branch, tag, or commit SHA to build against |
KLANGK_REPO |
https://github.com/mcdonc/klangk.git |
Klangk repo URL |
KLANGK_HOST_IMAGE |
ghcr.io/mcdonc/klangk/klangk-host-custom |
Output image name |
KLANGK_PLATFORM |
linux/amd64 |
Target platform |
KLANGK_SSL_CERT_DIR |
./ssl |
Directory containing .pem/.crt CA certs to inject into both images |
Plugins¶
Edit plugins.yaml to add or remove plugins. The default set includes the built-in plugins: celebrate, beep, pig-latin, word-count, browser-fetch, bobdobbs.
To add an external plugin:
See the Creating Plugins reference for plugin structure details.
Custom CA Certificates¶
Place .pem or .crt files in the ssl/ directory (or set KLANGK_SSL_CERT_DIR). They are installed into the system CA store of both the host and workspace images. This is needed when services (e.g., Logfire, OIDC providers) use certificates signed by a private CA. The ssl/ directory is gitignored — certs must be provided at build time.
OIDC Authentication¶
To enable OIDC login, create an oidc.yaml in the customize/ directory (gitignored), then mount it at runtime:
docker run -d \
...
-v ./oidc.yaml:/home/klangk/oidc.yaml:ro \
-e KLANGK_OIDC_CONFIG=/home/klangk/oidc.yaml \
ghcr.io/mcdonc/klangk/klangk-host-custom
If your OIDC provider requires a custom CA certificate (e.g. ca_cert: cacert.pem in oidc.yaml), place the PEM file at cacert.pem in the customize/ directory (gitignored). The docker-compose.yml mounts it into the container at /home/klangk/cacert.pem.
See the OIDC documentation for the config file format.
OIDC Login Hook¶
The customize/ directory includes a sample login hook (login_hook.py) that restricts OIDC logins to invited users. Set the environment variable to activate it:
The hook works because the Dockerfile copies login_hook.py into the backend's Python path (/home/klangk/src/backend/), making it importable by the OIDC login system.
The example hook's logic:
- If the user's most recent invitation is revoked, login is blocked (even if they have an existing account)
- If the user's most recent invitation is pending or accepted, login is allowed
- If the user has no invitation but has an existing account, login is allowed
- If the user has no invitation and no account, login is blocked
Re-inviting someone after a revocation creates a new pending invitation that overrides the revocation.
Running¶
docker run -d \
-p 8995:8995 \
-v ./data:/home/klangk/data \
-v ./mount:/home/klangk/mount \
-v ./oidc.yaml:/home/klangk/oidc.yaml:ro \
-v ./cacert.pem:/home/klangk/cacert.pem:ro \
--cap-add SYS_ADMIN \
--device /dev/fuse \
--device /dev/net/tun \
--security-opt seccomp=unconfined \
--security-opt systempaths=unconfined \
-e KLANGK_NGINX_PORT=8995 \
-e KLANGK_PORT=8997 \
-e KLANGK_DEFAULT_USER=admin@example.com \
-e KLANGK_DEFAULT_PASSWORD=changeme \
-e KLANGK_JWT_SECRET=change-this-to-a-random-secret \
-e KLANGK_PREVENT_INSECURE_JWT_SECRET=1 \
-e KLANGK_DATA_DIR=/home/klangk/data \
-e KLANGK_LLM_BASE_URL=https://ollama.com/v1 \
-e KLANGK_LLM_API_KEY=your-api-key \
-e KLANGK_LLM_MODEL=gemma4:31b \
-e KLANGK_INSTANCE_ID=default \
-e KLANGK_OIDC_CONFIG=/home/klangk/oidc.yaml \
-e KLANGK_AUTH_MODES=both \
-e KLANGK_OIDC_LOGIN_HOOK=login_hook.require_invitation \
-e KLANGK_DISABLE_REGISTRATION=1 \
-e KLANGK_DNS_SERVERS=100.100.100.100,8.8.8.8 \
-e KLANGK_ALLOWED_MOUNT_ROOTS=/home/klangk/mount \
-e KLANGK_SMTP_HOST=smtp.example.com \
-e KLANGK_SMTP_USER=you@example.com \
-e KLANGK_SMTP_PASSWORD=your-smtp-password \
-e KLANGK_SMTP_FROM=noreply@example.com \
-e LOGFIRE_BASE_URL=https://logfire-api.pydantic.dev \
-e LOGFIRE_TOKEN=your-logfire-token \
-e LOGFIRE_ENVIRONMENT=production \
ghcr.io/mcdonc/klangk/klangk-host-custom
Or use docker-compose.yml with a .env file:
services:
klangk:
image: ghcr.io/mcdonc/klangk/klangk-host-custom
ports:
- "8995:8995"
volumes:
- ./data:/home/klangk/data
- ./mount:/home/klangk/mount
- ./oidc.yaml:/home/klangk/oidc.yaml:ro
- ./cacert.pem:/home/klangk/cacert.pem:ro
cap_add:
- SYS_ADMIN
devices:
- /dev/fuse
- /dev/net/tun
security_opt:
- seccomp=unconfined
- systempaths=unconfined
environment:
KLANGK_NGINX_PORT: "8995"
KLANGK_PORT: "8997"
KLANGK_DEFAULT_USER: admin@example.com
KLANGK_DEFAULT_PASSWORD: changeme
KLANGK_JWT_SECRET: change-this-to-a-random-secret
KLANGK_PREVENT_INSECURE_JWT_SECRET: "1"
KLANGK_DATA_DIR: /home/klangk/data
KLANGK_LLM_BASE_URL: https://ollama.com/v1
KLANGK_LLM_API_KEY: your-api-key
KLANGK_LLM_MODEL: gemma4:31b
KLANGK_INSTANCE_ID: default
KLANGK_OIDC_CONFIG: /home/klangk/oidc.yaml
KLANGK_AUTH_MODES: both
KLANGK_OIDC_LOGIN_HOOK: login_hook.require_invitation
KLANGK_DISABLE_REGISTRATION: "1"
KLANGK_DNS_SERVERS: 100.100.100.100,8.8.8.8
KLANGK_ALLOWED_MOUNT_ROOTS: /home/klangk/mount
KLANGK_SMTP_HOST: smtp.example.com
KLANGK_SMTP_USER: you@example.com
KLANGK_SMTP_PASSWORD: your-smtp-password
KLANGK_SMTP_FROM: noreply@example.com
LOGFIRE_BASE_URL: https://logfire-api.pydantic.dev
LOGFIRE_TOKEN: your-logfire-token
LOGFIRE_ENVIRONMENT: production