Run Kazari

Configuration

On this page

Kazari reads one YAML file plus environment variables, validates the result at startup and refuses to start with a list of every problem it found, each named by its key. There are no settings stored in the database: to change a limit, edit the file and restart (it takes under a second). kazari admin settings (or GET /v1/settings on the admin API) prints the effective configuration with secrets redacted.

deploy/kazari.yaml is a complete example with every default, deploy/dev.yaml the local development setup and deploy/kazari.env.example the template for secrets.

Where the file comes from#

kazari serve uses the first of:

  1. --config PATH (the file must exist);
  2. $KAZARI_CONFIG;
  3. /etc/kazari/kazari.yaml, if it exists;
  4. no file: defaults plus environment variables.

A key the server does not know is an error, so a typo fails loudly rather than being ignored.

Format#

Sections are nested mappings; the keys and their nesting are those in Keys. Sizes and durations are written as plain strings (50MiB, 336h), lists as YAML sequences.

base_domain: art.example.com
data_dir: /var/lib/kazari

tls:
  mode: acme
  email: [email protected]
  dns_provider: cloudflare
  dns:
    api_token: ${KAZARI_TLS_DNS_API_TOKEN}

auth:
  admin_token: ${KAZARI_AUTH_ADMIN_TOKEN}
  oidc:
    issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
    client_id: 00000000-0000-0000-0000-000000000000
    client_secret: ${KAZARI_AUTH_OIDC_CLIENT_SECRET}
    allowed_domains: [example.com]

Environment references#

Any value may refer to an environment variable:

WrittenBecomes
${NAME}The value of NAME
${NAME:-default}The value of NAME, falling back to default
$$A literal $

A reference to a variable that is not set and has no default stops startup with an error naming the variable. deploy/dev.yaml uses a default so that development needs no secrets:

auth:
  admin_token: ${KAZARI_AUTH_ADMIN_TOKEN:-dev-admin-token}

The .env file#

kazari serve also loads variables from a dotenv file, the first of:

  1. --env-file PATH;
  2. $KAZARI_ENV_FILE;
  3. ./.env in the working directory, if it exists.

One KEY=VALUE per line; # starts a comment, export before the key is allowed, and a value may be wrapped in single or double quotes:

# .env
KAZARI_AUTH_ADMIN_TOKEN=3f9c2e...
export KAZARI_TLS_DNS_API_TOKEN="your-cloudflare-token"
KAZARI_LISTEN_PROXY_MODE=true

A variable already set in the process environment wins over the file. Variables from the file feed both ${NAME} references and the KAZARI_* overrides below. The file holds secrets: keep it mode 0600 and out of version control. Under systemd the unit reads /etc/kazari/kazari.env with EnvironmentFile=, so those variables arrive as process environment.

The CLI reads ./.env too, for its own variables (cli.md).

Precedence#

From lowest to highest:

  1. built-in defaults;
  2. the YAML file, after ${} references are expanded;
  3. KAZARI_* overrides, from the process environment or the .env file.

Validation then checks the result.

Environment overrides#

Every key has an environment variable: KAZARI_ followed by the key’s full path, upper-cased, with dots replaced by underscores. An override wins over the file, whether or not the file mentions the key, so a deployment configured entirely from the environment needs no file at all.

KeyVariable
base_domainKAZARI_BASE_DOMAIN
listen.proxy_modeKAZARI_LISTEN_PROXY_MODE
tls.dns_providerKAZARI_TLS_DNS_PROVIDER
tls.dns.api_tokenKAZARI_TLS_DNS_API_TOKEN
limits.max_version_bytesKAZARI_LIMITS_MAX_VERSION_BYTES
auth.oidc.allowed_domainsKAZARI_AUTH_OIDC_ALLOWED_DOMAINS

The secrets in deploy/kazari.yaml use the same names in their ${} references, so one variable serves either way.

Value formats:

  • Booleans: true, false, 1, 0 (anything Go’s strconv.ParseBool takes).
  • Lists: comma-separated, spaces trimmed: KAZARI_AUTH_OIDC_ALLOWED_DOMAINS=example.com,example.org. A variable replaces the whole list.
  • Sizes: an integer with an optional unit, case-insensitive, optional space: B, KiB, MiB, GiB, TiB (powers of 1024) or KB, MB, GB, TB (powers of 1000). "50MiB", "2 GiB", 52428800.
  • Durations: Go durations ("15m", "336h", "1h30m") with an optional leading whole number of days: "14d", "1d12h".

Setting any KAZARI_AUTH_OIDC_* variable switches on the auth.oidc section, with its defaults, even when the file has none.

Do not confuse the server’s variables with the CLI’s: KAZARI_URL, KAZARI_TOKEN, KAZARI_ADMIN_URL and KAZARI_ADMIN_TOKEN configure the client (cli.md); the server’s admin token is KAZARI_AUTH_ADMIN_TOKEN.

Keys#

Top level#

KeyTypeDefaultMeaning
base_domainstringnoneDomain artifacts live under, as <slug>.<base_domain>. Lower-case host name, no scheme, port or trailing dot. Required.
data_dirstringnoneHolds kazari.db, blobs/, uploads/ and certmagic/. Required. The systemd unit and the Docker image set /var/lib/kazari.
external_schemestringhttpsScheme of the public URLs Kazari prints and returns: https or http.
external_portinteger0Port appended to public URLs when not the scheme’s default; 0 omits it. 8080 gives http://demo.localhost:8080/.

external_scheme and external_port only shape the URLs in responses; they do not change what Kazari listens on.

listen#

KeyTypeDefaultMeaning
httpsstring:443TLS listener address. Required unless proxy_mode is on.
httpstring:80Plain listener. Outside proxy mode it answers /health and redirects every request for the base domain or its subdomains to HTTPS (301); "" disables it. In proxy mode it is the only listener and is required.
proxy_modeboolfalseServe plain HTTP on http behind one TLS-terminating proxy and take the client IP from the last X-Forwarded-For entry, then X-Real-IP.

tls#

KeyTypeDefaultMeaning
modestringacmeacme, files or self-signed. See install.md.
emailstringnoneACME account contact. Required for acme outside proxy mode.
castringletsencryptletsencrypt, letsencrypt-staging or an https ACME directory URL.
dns_providerstringcloudflareDNS-01 provider: cloudflare, route53 or hetzner.
cert_filestring""PEM certificate chain for files. Must cover the base domain and its wildcard.
key_filestring""PEM private key for files.

tls.dns#

KeyTypeDefaultMeaning
api_tokensecret""DNS provider credential. Required for cloudflare and hetzner. For route53, leave empty to use the AWS default credential chain, or set ACCESS_KEY_ID:SECRET_ACCESS_KEY.

limits#

KeyTypeDefaultMeaning
max_version_bytessize50MiBTotal size of all files in one version. Also bounds a request body (plus 1 MiB of headroom), a bundle’s decompressed tar stream (the same) and one chunked upload.
max_file_bytessize25MiBSize of one file.
max_filesinteger2000Files in one version.
metadata_max_pairsinteger64Metadata key/value pairs per artifact.
write_rate_per_minuteinteger60Write and delete requests per minute per credential (burst of the same size), one budget across the user API and the MCP write tools; 429 (or an MCP rate_limited error) beyond it.
default_user_quotasize2GiBQuota given to users created without one, whether by an admin or by sign-in. See ops.md.
inline_guidance_bytessize2MiBQuoted in the MCP tool descriptions as the size above which a model should switch to chunked uploads. Advisory; nothing enforces it.
max_chunk_bytessize4MiBLargest chunk of a chunked upload, after base64 decoding on MCP.
upload_ttlduration1hHow long a chunked upload stays open before the uploads-expire job removes it.
max_open_uploadsinteger5Open chunked uploads per user.

lifecycle#

KeyTypeDefaultMeaning
default_graceduration336h (14 days)Grace period of a stale call that names none.
retentionduration720h (30 days)Time a soft-deleted artifact stays restorable before the sweep purges it.
tombstoneduration2160h (90 days)Time a purged slug stays reserved (and answers 410) before it is freed.
sweep_intervalduration15mHow often the sweep job runs.
gc_intervalduration1hHow often the gc job deletes unreferenced blobs.

See ops.md for how these combine.

serve#

KeyTypeDefaultMeaning
cspstringsee belowContent-Security-Policy sent with every artifact response; "" sends none.
allow_csp_overridebooltrueLet an artifact’s kazari.csp metadata replace csp for its own files.
reserved_slugsstring listsee belowSlugs no artifact may take. Each must be non-empty and lower-case.

The default CSP allows inline scripts and styles plus the common CDNs:

default-src 'self'; script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'none'

The default reserved slugs are api, mcp, admin, www, mail, ns1, ns2, autoconfig, autodiscover and localhost. If you replace the list, keep api, mcp and admin.

Each artifact is its own origin, so a looser per-artifact CSP only affects that artifact’s visitors; on a multi-tenant instance consider allow_csp_override: false.

auth#

KeyTypeDefaultMeaning
write_policystringsharedshared: anyone with artifacts:write may change any artifact. owner: only the owner and admins may.
admin_tokensecret""Bearer token accepted on the admin host. Empty disables it, leaving admin users’ credentials that carry the admin scope.

auth.oidc#

Present means “users sign in through this OpenID Connect provider”; absent means the OAuth consent page asks for an API key instead. See identity.md.

KeyTypeDefaultMeaning
issuerstringnoneThe provider’s issuer URL, exactly as its discovery document states it. Must be https. Required.
client_idstringnoneClient ID of the app registered at the provider. Required.
client_secretsecretnoneClient secret. Required.
allowed_domainsstring list[]Verified email domains that may sign in.
auto_provisionbooltrueCreate a member on first sign-in from an allowed domain.
admin_emailsstring list[]Emails provisioned with the admin role.

admin#

KeyTypeDefaultMeaning
listenstring""Optional listener, plain HTTP, that serves the admin API, /health and /metrics whatever the Host header, for example 127.0.0.1:9443. When it is set, admin.<base> is no longer served on the public listener (it answers 404 there), so point the admin CLI at this address.

log#

KeyTypeDefaultMeaning
levelstringinfodebug, info, warn or error.
formatstringtexttext or json (slog handlers).

Validation rules you may hit#

All of these are checked together; the error lists every failure as key: problem.

  • base_domain is required, lower-case, without scheme, port or trailing dot, and made of labels of letters, digits and hyphens.
  • data_dir is required.
  • external_scheme is http or https; external_port is 0 to 65535.
  • Outside proxy mode listen.https is required; in proxy mode listen.http is required.
  • tls.mode is one of the three modes. Outside proxy mode:
    • acme needs tls.email, a valid tls.ca, a known tls.dns_provider and, except for route53, tls.dns.api_token;
    • files needs tls.cert_file and tls.key_file.
  • Every number and duration in limits and lifecycle must be greater than 0. In particular write_rate_per_minute cannot be set to 0 to disable rate limiting, and default_user_quota cannot be 0; give individual users an unlimited quota through the admin API instead (kazari admin users update <handle> --quota 0).
  • limits.max_file_bytes and limits.max_chunk_bytes must not exceed limits.max_version_bytes.
  • auth.write_policy is shared or owner.
  • auth.admin_token, when set, is at least 32 characters, unless base_domain is localhost or ends in .localhost (a development instance that only its own machine can reach).
  • With auth.oidc: issuer is an https URL, client_id and client_secret are set.
  • log.level and log.format are among the listed values.

Secrets#

Three keys are secrets: tls.dns.api_token, auth.admin_token and auth.oidc.client_secret. They are shown as "***" by the settings endpoint. Never write them into the YAML file, which under systemd must be world-readable: refer to them as ${NAME} (or leave them out and set the KAZARI_* override) and supply the variables through /etc/kazari/kazari.env (mode 0600, read by systemd as root) for the unit, .env in development, --env-file for Docker, or your secret manager’s equivalent.

Generate the admin token with openssl rand -hex 32; a token shorter than 32 characters stops startup. Once an admin user has an API key with the admin scope, you can retire the token: delete the admin_token line from the file (a ${} reference to a variable that is no longer set stops startup) and unset KAZARI_AUTH_ADMIN_TOKEN.