Deployment
This guide is for operators running the self-hosted Dakera server in production. It covers the supported ways to run v0.12.3 (Docker, Docker Compose, Kubernetes with Helm, and systemd), then what every deployment needs: TLS termination, the data volume, health probes, backups, monitoring, upgrades and rollback.
Every setting is an environment variable; the complete list is in the Configuration reference.
DAKERA_CLUSTER_SECRET; from v0.12.2 sessions end after 4 hours idle by default and writes are validated more strictly; from v0.12.3 the images have no curl, so health checks use dakera healthcheck). To go back, see Rolling back to v0.11. Pin a version with ghcr.io/dakera-ai/dakera:0.12.3.Docker (single node)
docker run -d \
--name dakera \
--restart unless-stopped \
-p 3000:3000 \
-e DAKERA_PORT=3000 \
-e DAKERA_ROOT_API_KEY=your-secret \
-e DAKERA_STORAGE=filesystem \
-e DAKERA_STORAGE_PATH=/data \
-v dakera-data:/data \
-v dakera-models:/app/models \
ghcr.io/dakera-ai/dakera:0.12.3
/data is the data root (write-ahead log, tiers, knowledge graph, filesystem storage): mount one volume there. /app/models holds only models the image does not ship, so you can leave it out with the default models. Authentication is on by default; send the key as Authorization: Bearer or X-API-Key. Add -p 50051:50051 for gRPC, which requires an API key.
The image ships its default models (bge-large and the bge-reranker-v2-m3 reranker) already converted, so a default start needs no network, downloads nothing and works with a read-only root filesystem. See Model store and air-gapped installs below.
Docker Compose (recommended)
The recommended way to run a single production node. This compose file runs Dakera on S3 storage provided by MinIO; a one-off minio-setup service creates the bucket, because the server checks at startup that the bucket exists and refuses to start without it.
# docker-compose.yml
services:
dakera:
image: ghcr.io/dakera-ai/dakera:0.12.3
restart: unless-stopped
ports:
- "3000:3000"
environment:
DAKERA_PORT: 3000
DAKERA_ROOT_API_KEY: ${DAKERA_ROOT_API_KEY}
DAKERA_STORAGE: s3
DAKERA_S3_ENDPOINT: http://minio:9000
DAKERA_S3_BUCKET: dakera
DAKERA_S3_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
DAKERA_ENCRYPTION_KEY: ${DAKERA_ENCRYPTION_KEY}
RUST_LOG: info
volumes:
- dakera-data:/data
depends_on:
minio-setup:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "dakera", "healthcheck", "/health/ready"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10m
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 5
minio-setup:
image: minio/mc:latest
depends_on:
minio:
condition: service_healthy
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
entrypoint: >
/bin/sh -c "mc alias set local http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD
&& mc mb --ignore-existing local/dakera"
volumes:
dakera-data:
minio-data:
# Generate secrets and start
printf "DAKERA_ROOT_API_KEY=%s\nDAKERA_ENCRYPTION_KEY=%s\nMINIO_ROOT_USER=minioadmin\nMINIO_ROOT_PASSWORD=%s\n" \
"$(openssl rand -hex 32)" "$(openssl rand -hex 32)" "$(openssl rand -hex 16)" > .env
docker compose up -d
Kubernetes
helm install dakera oci://ghcr.io/dakera-ai/dakera-helm/dakera \
--version 0.12.3 --namespace dakera --create-namespace \
--set dakera.rootApiKey="$(openssl rand -hex 32)" \
--set minio.rootPassword="$(openssl rand -hex 16)"
Chart 0.12.3 deploys server 0.12.3 and Dashboard 0.5.2 (pin with --version 0.12.3). From 0.12.3 the server runs with a read-only root filesystem by default (dakera.readOnlyRootFilesystem) and only Dakera pods may reach the built-in MinIO (minio.networkPolicy). Settings without a dedicated value go through dakera.extraEnv; models can be pulled by an init container before the pod serves:
# values.yaml
dakera:
extraEnv:
- name: DAKERA_MODEL
value: bge-m3
models:
pull: [configured] # init container runs dakera models pull
persistence:
enabled: true # keep the model cache across pods
| Key | Default | Description |
|---|---|---|
dakera. | /data | DAKERA_; the persistence volume (dakera., 20Gi) is mounted here |
dakera. | uid/gid 1000, fsGroup: 1000 | Makes the mounted volumes writable by the image's user; set {} where the platform assigns uids (OpenShift) |
dakera. | false | Read-only root filesystem (data root, model cache and /tmp are volumes) |
dakera. | empty | DAKERA_ (or dakera.); required on every node in cluster mode from v0.12.1 |
dakera. | false | A server locks its data root, so one release is one node; enable only with persistence. and an external S3 |
rollback. | false | Renders the one-off dakera downgrade Job |
Full values: charts/dakera/values.yaml · ArtifactHub. Kustomize manifests available in dakera-deploy/k8s/.
Reverse proxy & TLS termination
Nginx
# /etc/nginx/sites-available/dakera
server {
listen 443 ssl http2;
server_name dakera.example.com;
ssl_certificate /etc/letsencrypt/live/dakera.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dakera.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE support (required for event streaming)
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
}
}
# Redirect HTTP → HTTPS
server {
listen 80;
server_name dakera.example.com;
return 301 https://$host$request_uri;
}
# Obtain certificate with certbot
sudo certbot --nginx -d dakera.example.com
Caddy
# Caddyfile — automatic HTTPS with Let's Encrypt
dakera.example.com {
reverse_proxy localhost:3000 {
flush_interval -1 # required for SSE streaming
}
}
Caddy automatically provisions and renews TLS certificates via Let's Encrypt — no additional setup required.
Self-hosted VPS
Run the Dakera binary directly on a Linux host (for example Ubuntu or Debian) under systemd. This suits a single node on dedicated hardware. With WorkingDirectory=/opt/dakera and no DAKERA_STORAGE_PATH, the data root is /opt/dakera/data.
# /etc/systemd/system/dakera.service
[Unit]
Description=Dakera Agent Memory Engine
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=dakera
Group=dakera
WorkingDirectory=/opt/dakera
ExecStart=/opt/dakera/dakera
Restart=always
RestartSec=5
LimitNOFILE=65535
# Environment
EnvironmentFile=/opt/dakera/.env
[Install]
WantedBy=multi-user.target
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable dakera
sudo systemctl start dakera
# Check status
sudo systemctl status dakera
curl http://localhost:3000/health
Cloud deployment
| Platform | Service | Quick start |
|---|---|---|
| AWS | ECS Fargate | Task definition with ghcr., EFS volume for /data, ALB for TLS |
| GCP | Cloud Run | gcloud run deploy dakera -- |
| Azure | Container Instances | az container create -- |
| Fly.io | fly launch | fly launch -- with persistent volume |
| Railway | Docker image service | Deploy ghcr. with a volume mounted at /data |
Resource sizing
Size from your own data and load: there is no published table by memory count. These are the figures measured for v0.12.0, with where they were taken.
| What | Measured | Where |
|---|---|---|
| HNSW memory per 1024-d vector | 2.76 KB (4.81 KB in v0.11.108), recall@10 unchanged | BEIR Quora, 50k vectors |
| Container memory after reranking | 0.65 GB, flat across top_k 1 to 32 (4.98 GB in v0.11.108) | 4 CPU / 8 GiB container, bge- |
Recall with reranking, top_k 16 | 6.2 s, 24 CPU-s (11.5 s, 45 CPU-s in v0.11.108) | CPU-only, 4 vCPU / 8 GiB container, bge- (568M-parameter cross-encoder), compared with v0.11.108 on the same machine; GPU or hosted rerankers are a different deployment class |
| Memory, ingest and recall p50 of a three-conversation run | 0.97 GB, 46 s, 1.8 s (2.4 GB, 124 s, 3.8 s in v0.11.108) | same workload on v0.11.108 and v0.12.0 |
| All multimodal models loaded | 530 MiB process heap (excluding memory-mapped model files) | multimodal footprint run |
| Default image | 783 MB (CPU, amd64), ready 4.4 s after start | no download |
| Re-embed after upgrading from v0.11 | about 35 minutes per 10,000 memories on bge-large; recall is served throughout | conversational turns |
| Late-interaction recall p50 | 0.10 s at 1k memories, 0.23 s at 10k | steady state, colbert-small |
| Image indexing | about 10.7 s per page | CPU |
Still being measured: the 50k capacity re-run, the embedder benchmark, the load sweep and durable writes per second. Media jobs reserve their working memory before they start and answer 503 with Retry-After when it does not fit. RocksDB (the tiered hot tier) benefits from high random-read IOPS; use SSD or NVMe. DAKERA_L1_CACHE_SIZE takes a vector count or a byte size such as 512MB.
Data root and volumes
Every local path derives from one data root, DAKERA_STORAGE_PATH (default ./data; the images set /data and declare it, with /app/models, as volumes). Before v0.12 the image's root was /app/data: a server that finds data there and none under /data keeps using it where it is and warns, naming the variable that pins it (data_location in /health config_warnings). Nothing is moved. Keep the whole data root on a persistent volume, also with S3 storage: it holds the knowledge graph and audit events.
| Path | Holds |
|---|---|
{root}/wal, {root}/ | Write-ahead log and snapshots |
{root}/hot | RocksDB hot tier |
{root}/ | Warm tier |
{root}/ | Durable replication outbox (cluster mode) |
{root}/ | Cluster node id, generated once when DAKERA_ is unset |
{root}/. | Telemetry instance id (see Telemetry) |
{root}/. | Held while a server runs: a second server on the same root refuses to start |
DAKERA_ROCKSDB_PATH, DAKERA_CACHE_DIR, DAKERA_WAL_DIR, DAKERA_WAL_SNAPSHOT_DIR and DAKERA_GRAPH_PATH pin each path; DAKERA_DATA_DIR is an alias of DAKERA_STORAGE_PATH (two different values are an invalid value). Data at a pre-0.12 location is used where it is, with a warning.
- Tiered storage (
DAKERA_TIERED_STORAGE, S3 as the cold tier): the hot tier is now RocksDB (DAKERA_HOT_TIER=rocksdb, the default) and its writes are fsynced (DAKERA_ROCKSDB_SYNC, defaulttrue), so a crash no longer loses acknowledged writes not yet in cold;DAKERA_HOT_TIER=memorykeeps the old tier. Deletes are journaled and survive an S3 outage. - S3:
DAKERA_S3_TIMEOUT_SECS(default 30) bounds control requests andDAKERA_S3_IO_TIMEOUT_SECS(default 60) each data call. Cluster nodes must not share a bucket: each node writes a heartbeat marker, and another node's live marker refuses a fresh bucket (exit 78). - Cluster:
DAKERA_CLUSTER_SECRETis required on every node, fresh install or upgraded (v0.12.1; a node without it exits with code 78), andDAKERA_CLUSTER_NODE_IDon a fresh install. Set the secret on every node and upgrade them in one window (see High Availability).
Health checks
Since v0.12 the REST port is bound before models load, so a model that has to download no longer sits behind a closed port (a liveness probe used to restart the process, which downloaded again from zero). Use the two probe routes:
| Route | Answers | Use for |
|---|---|---|
GET / | 200 as soon as the port is bound | Liveness |
GET / | 200 once the embedding model is loaded and storage answers; 503 with what is loading and per-file download progress before that | Readiness |
GET /health | status (healthy or degraded), version; lists degraded components and config_warnings; 503 with "status": "starting" while models load | Humans and dashboards |
Every other request answers 503 with Retry-After: 5 until the models are loaded. The reranker, and whisper and the vision model when enabled, load in the background and do not gate readiness.
curl http://localhost:3000/health/ready
# {"ready":true,"version":"0.12.3","checks":{"storage":{"status":"ok","message":null},"embedding_engine":{...},"tiered_engine":{...}}}
Docker
The published images already carry a HEALTHCHECK with a 10-minute start period: dakera healthcheck, a probe built into the server binary (the images ship without curl from v0.12.3). It connects to the node's own REST port, requests the path you give it (default /health) and exits 0 only on 200. For your own compose file:
healthcheck:
test: ["CMD", "dakera", "healthcheck", "/health/ready"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10m
Kubernetes
# In pod spec — the startup probe covers port binding (write-ahead-log replay)
startupProbe:
httpGet:
path: /health/live
port: 3000
periodSeconds: 10
failureThreshold: 60
livenessProbe:
httpGet:
path: /health/live
port: 3000
periodSeconds: 30
readinessProbe:
httpGet:
path: /health/ready
port: 3000
periodSeconds: 10
The Helm chart (dakera-helm) is already set up this way (liveness on /health/live behind a 10-minute startup probe, readiness on /health/ready) and sets a pod security context of uid/gid 1000 with fsGroup: 1000.
Model store and air-gapped installs
Models other than the shipped defaults (another embedding model such as bge-m3, whisper, the vision model, GLiNER entity extraction) download on first use into the model cache, HF_HOME (/app/models in the images, a VOLUME). Mount a volume there to download each model once. Every file is checked against a pinned SHA-256. Downloads honour HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, NO_PROXY, HF_ENDPOINT (a mirror) and HF_HUB_OFFLINE.
# What is on disk, and what can go
docker exec dakera dakera models list
docker exec dakera dakera models prune --dry-run
# Fetch a model ahead of the first start
docker run --rm -v dakera-models:/app/models ghcr.io/dakera-ai/dakera:0.12.3 models pull bge-m3
# Air-gapped: seed on a connected machine, copy to the host, mount at /app/models
docker run --rm -v "$PWD/models:/seed" ghcr.io/dakera-ai/dakera:0.12.3 models pull --dir /seed bge-m3 whisper vision
# ...then run with -v ./models:/app/models -e HF_HUB_OFFLINE=1
dakera models prune frees them, but do not prune until you are staying on v0.12 — v0.11.108 reads those files. The full guide, with proxies, mirrors, Helm and derived images, is Models and Docker.Backup & restore
super_admin scope (a key pinned to namespaces, or an unrestricted admin key, gets 403). A stored, enabled backup schedule now runs; the live schedule is no longer replaced by a restore. Backups taken under v0.12 are restored by v0.12 and then downgraded — see Rolling back to v0.11.Dakera provides backup and restore through the admin API. A backup holds the namespaces' records with their sidecars, attachments and knowledge-graph edges, the API-key records and the encryption keyring; text is kept sealed (a backup never holds plaintext _text). Creating, listing and polling need global admin; download, upload and restore need global super_admin.
# Create a backup (202; the backup runs in the background)
curl -X POST http://localhost:3000/admin/backups \
-H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"before-upgrade","compression":"zstd","encrypt":false}'
# {"backup":{"backup_id":"...","name":"before-upgrade","status":"...", ...}}
# Poll it, or list all
curl http://localhost:3000/admin/backups/BACKUP_ID -H "Authorization: Bearer $DAKERA_ROOT_API_KEY"
curl http://localhost:3000/admin/backups -H "Authorization: Bearer $DAKERA_ROOT_API_KEY"
# Download a completed backup (a gzip-compressed JSON bundle)
curl -o backup.json.gz \
http://localhost:3000/admin/backups/BACKUP_ID/download \
-H "Authorization: Bearer $DAKERA_ROOT_API_KEY"
# Restore a stored backup (202 with a restore_id; add "overwrite": true for the exact point in time)
curl -X POST http://localhost:3000/admin/backups/restore \
-H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"backup_id":"BACKUP_ID","overwrite":false}'
# Restore from a downloaded bundle (raw gzip body; needs S3 storage)
curl -X POST http://localhost:3000/admin/backups/upload \
-H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
--data-binary @backup.json.gz
compressionisnoneorzstd;lz4is a400.encrypt: trueseals each file with AES-256-GCM under a key derived fromDAKERA_ENCRYPTION_KEYand is a400without that key. Both are recorded in the backup's manifest, which also carries the taking node'snode_id.incrementalis reported asfull.- Without
overwritea restore is the v0.11 merge (the backup's records are written over the current copies, later records kept); with"overwrite": trueit is the backup's point in time. Revoked API keys stay revoked, and the keyring only gains keys it lacks. One restore runs at a time (409), and serving is not paused: restore in a maintenance window. With one bucket per node, a restore or download on a node that does not hold the backup's files is409naming the owning node. - A backup that a stop interrupted is marked failed at the next start.
Scheduled backups
# Daily at 02:00, 30-day retention, zstd
curl -X POST http://localhost:3000/admin/backups/schedule \
-H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":true,"cron":"0 2 * * *","retention_days":30,"max_backups":30,"compression":"zstd"}'
Since v0.12 a stored, enabled schedule runs: one backup per due slot, on the leader in a cluster; retention_days and max_backups apply to scheduled backups only. An upgraded deployment with an enabled stored schedule gets a backup_schedule configuration warning at its first start.
Monitoring & observability
Dakera exposes Prometheus metrics at GET /metrics (no key; scrape it from inside your network and do not expose it publicly). GET /v1/ops/metrics returns the same exposition behind a key with global admin scope. The names below are emitted by the v0.12.0 server.
| Metric | Type | Description |
|---|---|---|
dakera_ | Counter | HTTP requests; refusals (429, 401/403, 408, 413, 503) and unmatched paths (path="unmatched") are counted |
dakera_ | Histogram | Request latency |
dakera_ | Gauge | In-flight requests |
dakera_ | Histogram | Vector query latency |
dakera_, dakera_, dakera_ | Counters | Memory operations by agent and namespace |
dakera_ | Gauge | Live memory count per agent |
dakera_ | Histogram | Per-stage recall latency |
dakera_ | Histogram | Embedding latency |
dakera_ | Counter | Errors; memory_budget and memory_ are memory refusals |
dakera_ | Gauge | 1 while a component is listed under degraded in /health |
dakera_ | Gauge | Number of config_warnings in /health |
dakera_, dakera_ | Counter, histogram | Lazy model loads by model and outcome (download included) |
dakera_ | Gauge | Working memory reserved by media jobs and entity extraction |
dakera_ | Counter | Backups by outcome (completed / failed) |
dakera_ | Counter | Write-ahead-log appends that failed |
dakera_ | Counter | Memories a read path skipped because their content did not open |
dakera_ | Gauge | Memories the post-upgrade re-embed still has to process |
dakera_, dakera_tiered_*, dakera_, dakera_ | various | Keyring and re-seal progress; tiered storage; RocksDB; replication (outbox, tombstones, dakera_) |
dakera_ | Gauge | Process memory |
Prometheus scrape config
# prometheus.yml
scrape_configs:
- job_name: dakera
scrape_interval: 15s
static_configs:
- targets: ["dakera:3000"]
metrics_path: /metrics
Alert recommendations
| Alert | Condition | Severity |
|---|---|---|
| Health check failing | up{job="dakera"} == 0 | Critical |
| 5xx rate | sum(rate(dakera_ | Warning |
| Shedding load | sum(rate(dakera_ | Warning |
| Component degraded | max by (component) (dakera_ | Warning |
| Configuration warnings | dakera_ | Warning |
| Backup failed | increase(dakera_ | Warning |
| Write-ahead-log failures | increase(dakera_ | Critical |
OpenTelemetry tracing
# Enable OTLP trace export
DAKERA_TRACING_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
OTEL_SERVICE_NAME=dakera
Telemetry
The self-hosted engine sends product telemetry to Dakera's PostHog project (EU) to show which versions, platforms and storage setups are in use, whether engines stay up and how often they are upgraded. It never reads or sends memory content, embeddings, queries, ids, names, API keys, file paths or URLs.
engine_started and engine_heartbeat carry the machine's hostname, and PostHog records the connecting IP address and derives a coarse location from it (GeoIP: country, region, city). Events: engine_started, engine_stopped, engine_crashed, engine_version_changed, and a heartbeat every 6 hours (v0.11: 24 hours). Everything sent is listed in Telemetry.Opt out
# Either variable turns telemetry off completely — nothing is counted, written or sent
DAKERA_TELEMETRY=0
DO_NOT_TRACK=1
| Variable | Default | Description |
|---|---|---|
DAKERA_ | on | 0, false, off or no disables telemetry |
DO_NOT_TRACK | — | 1 or true disables telemetry |
DAKERA_ | off | Log every batch (as JSON) before it is sent |
Log management
Dakera writes its logs to standard output; RUST_LOG sets the filter (the images set info). Audit records are one line per request, JSON by default (DAKERA_AUDIT_FORMAT).
# Log levels
RUST_LOG=info # production (recommended)
RUST_LOG=debug # development / troubleshooting
RUST_LOG=info,tower_http=debug # verbose HTTP logging only
Log retention
Rotate logs where they are collected. Under systemd they go to the journal (journalctl -u dakera; retention is set in journald.conf). Under Docker, set the logging driver's rotation:
# docker-compose.yml, on the dakera service
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
Upgrading
v0.12.3 upgrades in place from v0.11.108, v0.12.0, v0.12.1 or v0.12.2 on the same data and environment, and goes back to v0.11.108 with one command (dakera downgrade). Read Upgrading to v0.12 before you start — it lists what changes on purpose. In short:
- Take a backup:
POST /admin/backups. - Run the new image's
dakera --check-configagainst the deployment's environment and data volume; fix or note each warning. - Give gRPC clients an API key; check namespace-pinned keys and backup tooling against the new permissions (from v0.11.108).
- Cluster: set the same
DAKERA_CLUSTER_SECRETon every node and upgrade the nodes in one window (see High Availability). - Sessions: from v0.12.2 the server ends sessions idle for 4 hours (
DAKERA_SESSION_IDLE_TIMEOUT_SECS;0keeps them open until a client ends them). Check clients that write reserved metadata keys or over-size content against the v0.12.2 checks. - Health checks and admin tooling: from v0.12.3 probe with
dakera healthcheckinstead ofcurlinside the container, give tooling that callsPOST /ops/shutdownasuper_adminkey, and setDAKERA_AUTOPILOT_DEDUP_MODE=mergeif you rely on automatic deduplication (v0.12.3 checks). - Stop the running version and start v0.12.3 on the same data and environment.
- Nothing to do for the full-text indexes: v0.12.3 re-analyses indexes built by v0.11.108 in the background while they serve. (On v0.12.0 this was a manual step; see Full-text indexes.)
- Watch
/health:config_warnings,degraded,embed_migration.
# 1. Pull the new image
docker pull ghcr.io/dakera-ai/dakera:0.12.3
# 2. Restart the container on the same volumes and environment
docker compose down && docker compose up -d
# 3. Wait for readiness and verify the version
curl http://localhost:3000/health/ready
# {"ready":true,"version":"0.12.3","checks":{...}}
curl http://localhost:3000/health
# {"service":"dakera","status":"healthy","version":"0.12.3","degraded":[],"config_warnings":[], ...}
For systemd deployments: replace the binary at /opt/dakera/dakera, then sudo systemctl restart dakera. Always verify the readiness endpoint after upgrade. Clusters that already run v0.12.0, v0.12.1 or v0.12.2 with DAKERA_CLUSTER_SECRET upgrade node by node; otherwise set the secret and switch every node in one window (see High Availability). After an upgrade from v0.11.108, stored memories that need it are re-embedded once in the background (about 35 minutes per 10,000 memories on bge-large; nothing to do for minilm, gte-modernbert-base, bge-m3 or colbert-small); recall is served throughout.
Rolling back
Stop v0.12, run dakera downgrade once with the server's environment and volumes (docker run --rm <env and volumes> ghcr.io/dakera-ai/dakera:0.12.3 downgrade), read its JSON report (exit 0: the data is v0.11.108's; 1: run it again; 78: it refused and changed nothing), then start v0.11.108 (from v0.12.2 the downgrade also rewrites binary RocksDB hot-tier records as JSON). Going back to v0.12.2, v0.12.1 or v0.12.0 needs no downgrade. Details and what blocks a rollback: Rolling back to v0.11.
Production checklist
| Item | Priority | Notes |
|---|---|---|
Set DAKERA_ | Required | Use a cryptographically random 32+ char secret |
| TLS via reverse proxy | Required | Nginx or Caddy in front — never expose port 3000 directly |
| Persistent volume | Required | SSD-backed storage for DAKERA_ |
| Object storage | Recommended | MinIO or AWS S3 for durability and the cold tier |
| Encryption at rest | Recommended | Set DAKERA_ for AES-256-GCM |
Scrape /metrics | Recommended | Prometheus with alert rules for latency and health |
| Audit logging | Recommended | On by default (DAKERA_); ship the audit lines to your log store |
| Health check probes | Recommended | Docker HEALTHCHECK or K8s liveness/readiness probes |
| Scheduled backups | Recommended | Daily with 30-day retention via admin API |
| Set resource limits | Recommended | CPU/RAM limits per sizing guide above |
dakera -- before every change | Recommended | Lists what the next start would warn about or refuse (exit 0 or 78) |
Set RUST_LOG=info | Recommended | Avoid debug in production — verbose and slow |
Environment variable reference
The most-used variables, grouped by category. Every variable, with the values it accepts, is in the Configuration reference.
Core
| Variable | Default | Description |
|---|---|---|
DAKERA_HOST | 0.0.0.0 | Server bind address |
DAKERA_PORT | 3000 | REST API port |
DAKERA_ | — | Root authentication key (required) |
DAKERA_ | true | Enable gRPC API |
DAKERA_ | 50051 | gRPC server port |
DAKERA_ | 300 | Request timeout in seconds; also bounds every gRPC call |
DAKERA_ | 10485760 | Max request body size in bytes (10 MiB) |
DAKERA_ | 30 | Seconds a client has to send a request head; idle keep-alive and HTTP/2 connections close after it. Keep a proxy's upstream idle timeout below it (v0.12.3) |
DAKERA_ | unset | With authentication off: dotted host names the server answers (comma-separated, * = any); others get 421 (v0.12.3) |
DAKERA_ | 100000 | Longest memory content in UTF-8 bytes, on every memory write path (v0.12.2) |
DAKERA_ | 14400 (4 h) | Sessions idle this long are ended by the server; 0 = only sessions that set their own timeout are ended; at most 30 days (v0.12.2) |
DAKERA_ | 256 | SSE broadcast channel capacity |
RUST_LOG | info | Log level filter |
Storage
| Variable | Default | Description |
|---|---|---|
DAKERA_STORAGE | memory | Backend: memory, segmented, filesystem or s3; anything else refuses a fresh install |
DAKERA_ | ./data | The data root: every local path (write-ahead log, tiers, knowledge graph, filesystem storage) derives from it. DAKERA_DATA_DIR is an alias |
DAKERA_ | — | S3 bucket name (required for S3 backend) |
DAKERA_ | AWS_REGION | S3 signing region, required with DAKERA_ (MinIO accepts us-east-1) |
DAKERA_ | — | S3/MinIO endpoint URL |
DAKERA_ | false | Enable hot → warm → cold tiering |
DAKERA_ | 100000 vectors | Hot-tier budget: a vector count or a byte size such as 512MB |
DAKERA_ | 3600 | Idle seconds before a record moves from hot to warm |
DAKERA_ | 86400 | Idle seconds before a record moves from warm to cold (S3) |
DAKERA_ | {root}/ | Warm tier local disk path |
DAKERA_HOT_TIER | rocksdb | Tiered hot tier: rocksdb (durable) or memory |
DAKERA_ | true | fsync RocksDB hot-tier writes |
DAKERA_ / DAKERA_ | 30 / 60 | Per-attempt S3 deadlines for control requests / data calls |
Security
| Variable | Default | Description |
|---|---|---|
DAKERA_ | — | AES-256-GCM key (64 hex chars or passphrase ≥8 chars) |
DAKERA_ | unset (all origins; with authentication off, loopback origins only) | Comma-separated allowed origins |
DAKERA_ | false | Allow credentials in CORS; ignored with a startup warning unless origins are named (never with a wildcard) |
DAKERA_ | true | Enable Content Security Policy |
DAKERA_ | default- | CSP directive string |
DAKERA_ | true | Enable HSTS header |
DAKERA_ | 31536000 | HSTS max-age (1 year) |
DAKERA_ | DENY | X-Frame-Options value |
DAKERA_ | 100 | Rate limit: requests per second (RATE_LIMIT_RPS is the deprecated pre-v0.12 name, still honored with a warning) |
DAKERA_ | 50 | Rate limit: burst capacity (deprecated alias RATE_) |
Audit & tracing
| Variable | Default | Description |
|---|---|---|
DAKERA_ | true | Enable audit logging |
DAKERA_ | false | Log request bodies |
DAKERA_ | false | Log response bodies |
DAKERA_ | 4096 | Max body size to log (bytes) |
DAKERA_ | / | Path prefixes excluded from audit |
DAKERA_ | false | Enable OpenTelemetry OTLP export |
OTEL_ | http:// | OTLP exporter endpoint |
OTEL_ | dakera | Service name for traces |
Clustering
| Variable | Default | Description |
|---|---|---|
DAKERA_ | false | Enable multi-node cluster |
DAKERA_ | generated once | Stable node identifier (generated once and kept in {data root}/ when unset) |
DAKERA_ | — | Shared secret, at least 16 characters, the same on every node; authenticates node-to-node routes and gossip. Required in cluster mode, fresh install or upgraded node (v0.12.1) |
DAKERA_ | — | Comma-separated host:port seed addresses; required in cluster mode (legacy alias DAKERA_) |
DAKERA_ | 7946 | SWIM gossip port |
DAKERA_ | 0.0.0.0:7946 | Gossip bind address (legacy alias DAKERA_) |
DAKERA_ | — | Redis URL for distributed cache |
Inference & retrieval
| Variable | Default | Description |
|---|---|---|
DAKERA_ | false | Enable the ML query classifier. Set explicitly, a classifier that fails to initialize refuses a fresh install |
DAKERA_ | 0 (off; 0 to 1) | Temporal scoring boost factor |
DAKERA_ | 365 (30 to 3650) | Temporal window for recency scoring |
DAKERA_RRF_K | 60 | Reciprocal Rank Fusion k parameter |
DAKERA_ | 1000 | Minimum vectors for an ANN index (below it the scan is exact) |
DAKERA_ | 50 | HNSW index cache max entries |
AutoPilot & decay
| Variable | Default | Description |
|---|---|---|
DAKERA_ | true | Enable AutoPilot background tasks |
DAKERA_ | 6 | Deduplication cycle interval |
DAKERA_ | 12 | Consolidation cycle interval |
DAKERA_ | 0.93 | Cosine similarity threshold for dedup |
DAKERA_ | exponential | Decay strategy: exponential, linear, step |
DAKERA_ | 168 | Half-life for exponential decay |
DAKERA_ | 3600 | Decay engine cycle interval |
DAKERA_ | 0.01 | Importance floor: decay lowers importance to this value and does not delete the memory |
Related: Dakera Dashboard (admin UI) · Security hardening · High availability & clustering · Architecture · Full configuration reference · Self-hosted deployment walkthrough
Want zero-ops instead?
Dakera Cloud is coming — managed hosting, automatic upgrades, and built-in HA. No Docker to manage, no backup scripts, no ops overhead. Reserve your spot for founding pricing.