Klara · Self-Hosted Setup & Deployment Guide

Self-Hosting Klara

Everything needed to stand up the full Klara stack on your own server — the Word add-in, the backend, the tenant web app, and the admin console — using the Docker images already in this codebase. No external accounts of ours required; every credential below is one you create yourself.

Delivered as: Docker Compose Runs on: one Linux server Registry required: none — builds locally

Running this on your own Kubernetes cluster instead? See the Kubernetes (AKS) guide.

In this codebase — a real file, setting, or command you already have You provide — an account, domain, or credential from your own infrastructure

Klara is four containers plus three data stores, all reachable through one nginx edge. You run everything on a single server with Docker Compose — there's no cluster, no registry, and no dependency on any outside infrastructure beyond the AI and storage providers you choose in Part 2.

The edge
frontend (nginx)
:443 → Word add-in files
backend /api/v1/ · :8000
admin-backend /auth/ /api/ · :8080
admin-frontend /admin/ · :80

postgres · redis · qdrant · admin-postgres sit behind these, reachable only inside Docker's own network

The tenant web app (webapp) is the one exception — it gets its own port rather than routing through the edge above. More on that in Part 3.

Source: KlaraApp/word-addin/nginx.prod.conf, KlaraApp/backend/docker/docker-compose.yml

Before you start

  • A Linux server (VM, bare metal, or cloud instance) with Docker and Docker Compose v2 installed
  • A domain name you control, with its DNS A record pointed at that server
  • The Klara source code, delivered to you as a git repository or archive
  • An Azure Storage account (document uploads currently only support Azure Blob Storage — see Part 2)
  • Either an Azure OpenAI resource or AWS Bedrock access, for the AI analysis (see Part 2)
  • Optional an Azure AD App Registration, only if you want single sign-on from day one
1

Get the code onto the server

bash
git clone <the repository URL you were given> klara
cd klara

Everything from here on assumes you're working from the repository root, referred to below as klara/.

2

Configure the environment

One file holds every setting for the whole stack: KlaraApp/backend/.env. Start from the example and fill it in.

bash
cp KlaraApp/.env.production.example KlaraApp/backend/.env

Then open KlaraApp/backend/.env and work through it in the groups below.

App basics

SettingWhat to put
FRONTEND_URLYour real domain, HTTPS, trailing slash — e.g. https://klara.yourcompany.com/. This gets baked into the add-in at build time.
CORS_ORIGINSA JSON array with that same origin, no trailing slash — ["https://klara.yourcompany.com"]
JWT_SECRET_KEYGenerate with openssl rand -hex 32
POSTGRES_PASSWORDA strong password of your choosing
DATABASE_URLUpdate the password inside it to match POSTGRES_PASSWORD exactly — see the warning below
These two must match by hand. The compose file's default DATABASE_URL has the word changeme written into it literally — it does not automatically pick up whatever you set POSTGRES_PASSWORD to. Change one and forget the other, and the backend can't log in to its own database.

Leave API_BASE_URL alone — the build already fixes it to /api/v1 (a same-origin path, since the add-in and API are served from the same domain through the edge in Part §overview), regardless of what's in this file.

AI provider — pick one

Klara's document analysis runs on either Azure OpenAI or AWS Bedrock (Claude). Fill in one block, leave the other blank:

ProviderSettings
Azure OpenAIAZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_VISION_DEPLOYMENT, AZURE_OPENAI_API_VERSION
AWS BedrockAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, ANTHROPIC_MODEL
One catch either way: vector-search embeddings and vision-based layout QC currently only call Azure OpenAI, independent of which provider does the main analysis. If you're going Bedrock-only, you can still leave the Azure OpenAI fields blank — those two features just run in a mock/disabled mode until you add Azure OpenAI credentials later.

Document storage — required

AZURE_STORAGE_CONNECTION_STRING and AZURE_STORAGE_CONTAINER_PREFIX. As shipped, uploaded documents only support Azure Blob Storage — there's no alternative storage backend documented in this codebase today.

Single sign-on — optional

Leave AZURE_AD_TENANT_ID, AZURE_AD_CLIENT_ID, AZURE_AD_CLIENT_SECRET, and AZURE_AD_AUTHORITY blank to start with local email/password login only. Fill them in later from an Azure AD App Registration whenever you're ready to turn SSO on.

Admin console

SettingWhat to put
ADMIN_JWT_SECRETopenssl rand -hex 32
ADMIN_SECRETS_MASTER_KEYopenssl rand -base64 32
ADDIN_BRIDGE_KEYopenssl rand -hex 32 — a different value than JWT_SECRET_KEY
ADMIN_PLATFORM_PROVISIONING_KEYopenssl rand -hex 32
ADMIN_POSTGRES_PASSWORDA strong password, separate from the main POSTGRES_PASSWORD
ADMIN_FRONTEND_BASE_URLYour domain's origin only, no path — https://klara.yourcompany.com — see the note below
This one's already fixed in the example file. KlaraApp/.env.production.example now defaults ADMIN_FRONTEND_BASE_URL to your main domain (no subdomain, no path), matching how this compose setup's nginx actually serves the admin console — at /admin/ on the same domain as everything else, confirmed in nginx.prod.conf. The backend uses this value as a CORS allowed-origin (admin-console/backend/src/main/resources/application.yml), and an origin never includes a path. Still worth testing login end-to-end after your first deploy.

Source: KlaraApp/.env.production.example, KlaraApp/backend/.env.example, KlaraApp/admin-console/.env.example, KlaraApp/backend/docker/docker-compose.yml

3

Add a real TLS certificate

The Word add-in build bakes in a self-signed certificate so it runs out of the box in development — Office rejects self-signed certs in production, so this has to be replaced before real users install the add-in.

  1. Get a certificate for your domain (Let's Encrypt/certbot, your organization's CA, or one you already hold — this repo doesn't prescribe which).
  2. Name the files cert.pem and key.pem and place them on the server at /opt/klara-certs/.
  3. That's it — docker-compose.prod.yml already mounts that exact path into the frontend container at /etc/nginx/certs, overriding the baked-in self-signed pair automatically.
The tenant web app doesn't get this automatically. webapp is exposed on its own port and its nginx only serves plain HTTP inside the container — nothing in this compose file terminates TLS for it. Put your own reverse proxy (or a second certificate on the same box) in front of the webapp port before exposing it to real users; there's no built-in path for that here yet.

Source: KlaraApp/backend/docker/docker-compose.prod.yml, KlaraApp/word-addin/nginx.prod.conf, KlaraApp/word-addin/Dockerfile.prod

4

Build and start the stack

Run everything from KlaraApp/backend/docker/, using both compose files together and the admin profile (it's off by default — without this flag, admin-console never starts).

bash
cd KlaraApp/backend/docker

docker compose --env-file ../.env \
  -f docker-compose.yml -f docker-compose.prod.yml \
  --profile admin up -d --build

The --env-file ../.env flag points Compose's own variable substitution (the values used for things like the frontend's build args) at the same KlaraApp/backend/.env you just filled in — so you only maintain one file, not two.

First boot builds four images locally (no registry needed) and runs the database migration automatically — the backend's start command is alembic upgrade head followed by the app server. Expect the first run to take several minutes.

Source: KlaraApp/backend/docker/docker-compose.yml, docker-compose.prod.yml

5

Verify it's running

bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile admin ps
# every service should say "running (healthy)" or "running"

curl -sk https://klara.yourcompany.com/health
# backend liveness/readiness, proxied through the edge

curl -sk -o /dev/null -w "%{http_code}\n" https://klara.yourcompany.com/taskpane.html
curl -sk -o /dev/null -w "%{http_code}\n" https://klara.yourcompany.com/admin/
# both should print 200

Source: KlaraApp/word-addin/nginx.prod.conf (routes), Documentation/DEVOPS.md (health endpoints)

6

First login — and locking it down

In this Docker Compose setup, the demo-tenant bootstrap defaults to on (BOOTSTRAP_ENABLED defaults to true here — unlike the Kubernetes deployment, where it's off by default). That means a built-in login is already active the moment the stack comes up:

Tenantsystem
Emailsuperuser@klara.test
PasswordChangeMe123!

Log in at https://klara.yourcompany.com/admin/super-login and change that password immediately — it's public knowledge, sitting in this same source code, so leaving it as-is is a live risk, not a formality. From there, use the admin console's own on-screen flow to create your organization's real tenant and admin account.

If you don't want the demo tenant at all, set BOOTSTRAP_ENABLED=false in your .env before the first start, and provision your real tenant through the admin console's own onboarding flow instead.

Source: KlaraApp/backend/docker/docker-compose.yml (BOOTSTRAP_ENABLED default), Documentation/deployment.md

7

Get the Word add-in to your users

Once the manifest is pointing at your live domain (it already is — FRONTEND_URL from Part 2 got baked in at build time), distribute it one of these ways:

  • Microsoft 365 Admin Center (recommended) — Settings → Integrated Apps → Upload custom apps → upload the production manifest.xml and assign it to users or groups. It then just appears in their Word ribbon.
  • SharePoint App Catalog — upload manifest.xml there; users add it via Insert → Add-ins → My Organization.
  • Manual sideload — fine for testing a handful of machines, not for a real rollout.

The manifest is served directly from your domain at https://klara.yourcompany.com/manifest.xml.

What this guide doesn't cover

  • TLS for the tenant web app. As noted in Part 3, webapp has no certificate handling built in here — that's on you to add.
  • Backups. Nothing in this Docker Compose setup backs up postgres or admin-postgres automatically. Both store their data in named Docker volumes on the host — back those up (or the underlying disk) on your own schedule.
  • Scaling and failover. This is a single-server deployment. docker-compose.prod.yml sets every service to restart: always, so Docker brings a crashed container back on the same box — but there's no failover to a second machine if the server itself goes down.
  • Sizing. No official server-sizing numbers exist for this Docker Compose path. As a rough reference point, the equivalent Kubernetes deployment budgets roughly 250m CPU / 512Mi RAM per backend-type service under normal load — plan for a few GB of RAM and 2+ cores at minimum, plus disk for the Postgres, Qdrant, and document-upload volumes.

Quick reference

WhatWhere
Environment file (the one you edit)KlaraApp/backend/.env
Example to copy fromKlaraApp/.env.production.example
Compose files (run together)KlaraApp/backend/docker/docker-compose.yml + docker-compose.prod.yml
TLS certificate location on the host/opt/klara-certs/cert.pem and key.pem
Word add-in manifesthttps://<your-domain>/manifest.xml
Admin consolehttps://<your-domain>/admin/
Tenant web apphttp://<your-domain>:5173 (put your own TLS in front)
Health checkhttps://<your-domain>/health