yeke.io · docs

Installation

Run YEKE as a single container. Start with the Docker command below, then follow the sections on persistent settings, upgrades, Compose and Kubernetes.

Requirements

There are three requirements to get started. You can connect a Kubernetes cluster after installation.

  • A machine with Docker. Docker Compose is not required; use it if you'd like (further down).
  • openssl — or anything else that can produce 32 random bytes.
  • Access to Docker Hub — the nairotech/yeke image is public and multi-arch (amd64 + arm64).

There's no separate web container and no reverse proxy in front of it anymore. The UI, the API, and the agent tunnel all come from the same process, the same port; this image is the only thing you install — HTTPS comes from the same image too.

One command

Run the command below. You do not need to clone a repository, create a directory, or prepare a separate configuration file.

docker run -d --name yeke --restart unless-stopped -p 8080:8080 \
  -v yeke-data:/var/lib/yeke \
  nairotech/yeke:0.61.6

The data lives in a Docker volume named yeke-data. Docker creates it on first run and takes its ownership from the image: it comes out owned by the user core runs as (65532) and writable, with nothing for you to prepare. docker rm doesn't touch the volume; upgrades and backups are below.

Pin the version tag — don't write latest: core's version also determines the tag of the agent image that gets installed in your cluster. If the tag drifts, the agent ends up with a protocol mismatch. Released tags are on Docker Hub.

If you'd rather mount your own directory

To keep the data in a visible directory on the host, mount a directory instead of the volume. You create the directory yourself, and it has to belong to the user core runs as (65532); YEKE does not run as root and cannot write into a root-owned directory:

sudo install -d -m 700 -o 65532 -g 65532 /opt/yeke/data
docker run -d --name yeke --restart unless-stopped -p 8080:8080 \
  -v /opt/yeke/data:/var/lib/yeke \
  nairotech/yeke:0.61.6

Skipping the first line doesn't break the install quietly: core stops at startup and writes to its log which directory needs to belong to whom, along with the command to paste (docker logs yeke).

If you go this route, only the -v line changes in the commands elsewhere on this page: your directory in place of yeke-data.

Create the first administrator

On first boot there are no users at all; core generates a setup token and prints it to the log:

docker logs yeke | grep -i SETUP:

Open http://localhost:8080/ (with an HTTPS install, the address is https://<your-domain>/), paste the token, and pick a username and password. The token is valid for 60 minutes and can be used once; if it expires, docker restart yeke issues a new one.

The one thing to back up: the yeke-data volume

Everything the installation holds lives in that volume, in two files: core.db (cluster inventory, users, operation history, audit trail) and snapshot.key — the 32-byte encryption key core generates on first start. Direct-mode kubeconfigs, your AI provider keys, your hook secrets and rollback snapshots sit in the database encrypted with that key.

Back the two up together. A database backup without the key opens, but cannot read a single encrypted record — and the symptom is quiet: core does not crash, /healthz still returns 200, it just cannot reach your clusters. Measured on 2026-08-10.

If you would rather keep the key outside the data directory — so that a backup of the whole volume doesn't carry the key with it — supply YEKE_SNAPSHOT_KEY: see the next section.

Installing with HTTPS

The container can terminate HTTPS itself with your own certificate; you don't need a separate reverse proxy in front of it.

  • A DNS record — one that points your address at the machine YEKE runs on.
  • A certificate and key file — the server certificate your organization issued (plus any intermediates, full chain) and an unencrypted PEM key. YEKE does not obtain or renew the certificate; your organization provides it.

Put the certificate files in a directory and give it to the user core runs as (65532):

sudo install -d -m 755 /etc/yeke/certs
sudo cp tls.crt tls.key /etc/yeke/certs/
sudo chown -R 65532:65532 /etc/yeke/certs
sudo chmod 600 /etc/yeke/certs/tls.key
docker run -d --name yeke --restart unless-stopped -p 443:8443 \
  -v yeke-data:/var/lib/yeke \
  -v /etc/yeke/certs:/certs:ro \
  -e YEKE_TLS_CERT_FILE=/certs/tls.crt \
  -e YEKE_TLS_KEY_FILE=/certs/tls.key \
  -e YEKE_PUBLIC_URL=https://yeke.example.com \
  nairotech/yeke:0.61.6

With YEKE_TLS_CERT_FILE and YEKE_TLS_KEY_FILE set, YEKE listens for https on 8443 inside the container, published with -p 443:8443; leave both unset and the behavior is unchanged from today. Mount the certificate directory, not individual files: swap a file with mv and a single-file mount keeps serving the old one. If the directory or files aren't readable by 65532, core doesn't start; it logs which file, for which user, along with the fix (docker logs yeke). With TLS on, 8080 stays loopback-only inside the container — the healthcheck uses it — and isn't reachable even if published.

To rotate the certificate, replace the files: YEKE picks up the new one within 60 seconds, and open connections (the agent tunnel, an open terminal) don't drop. To switch over without waiting, run docker kill -s HUP yeke. If the new file is broken, YEKE keeps serving the old certificate and logs it.

https://<address>/healthz carries tls.notAfter and tls.daysLeft while TLS is on; once 30 days or fewer remain, a warning is logged once a day.

No HTTP/2. No 80→443 redirect — open the address with https://. YEKE does not obtain or renew the certificate (no ACME/Let's Encrypt automation); your organization provides it.

A certificate from a public CA needs no extra setup for the agent or Shell. With a certificate signed by your own internal CA, browsers trust it once the CA is distributed inside your organization; for how the agent inside the cluster, Shell and the CLI trust that CA without -k, see Internal CA.

If a reverse proxy or load balancer already terminates TLS in front of YEKE, you don't need this section: put YEKE behind that layer on 8080.

Every setting, out in the open

This example sets the public URL, license and an external key. Remove optional settings you do not need.

To keep the key outside, generate it once and write it to a file. These lines are optional: skip them and core generates the key inside the volume itself. If the file already exists this leaves it alone; re-running the line does not rotate your key:

sudo install -d -m 700 /etc/yeke
[ -f /etc/yeke/snapshot.key ] || openssl rand -hex 32 | sudo tee /etc/yeke/snapshot.key > /dev/null
sudo chmod 600 /etc/yeke/snapshot.key
docker run -d --name yeke --restart unless-stopped \
  -p 443:8443 \
  -v yeke-data:/var/lib/yeke \
  -v /etc/yeke/certs:/certs:ro \
  -v /etc/yeke/license.txt:/etc/yeke/license.txt:ro \
  -e YEKE_SNAPSHOT_KEY="$(sudo cat /etc/yeke/snapshot.key)" \
  -e YEKE_TLS_CERT_FILE="/certs/tls.crt" \
  -e YEKE_TLS_KEY_FILE="/certs/tls.key" \
  -e YEKE_PUBLIC_URL="https://yeke.example.com" \
  -e YEKE_LICENSE_PATH="/etc/yeke/license.txt" \
  nairotech/yeke:0.61.6

When YEKE_SNAPSHOT_KEY is set, the snapshot.key file in the data directory is neither read nor written — the variable always wins. Don't move an existing install onto this path: records encrypted with the key generated up to that point will not open with a different one.

If a reverse proxy in front of YEKE provides HTTPS, remove the certificate mount line and the two TLS lines, and write -p 8080:8080 instead of -p 443:8443. The YEKE_PUBLIC_URL and YEKE_LICENSE_PATH lines are optional too. If you don't have a license, or you'd rather upload it from the UI, remove the YEKE_LICENSE_PATH line and its mount line together. Removing just one of them creates a quiet mess: Docker, trying to mount a file that doesn't exist, creates an empty directory at that path instead; core can't read a directory, rejects the license, and keeps running as Community — the install looks fine, but it catches up with you later when you try to copy your actual license file to that path.

VariableRequiredWhat it does · what happens if left empty
YEKE_SNAPSHOT_KEYNo The 32-byte key (64 hex characters or base64) that encrypts stored credentials, AI keys, hook secrets and rollback snapshots. If empty, core generates one and writes it to the data directory (snapshot.key, mode 600), then reads it from there on every later start. Supplying it is how you keep the key outside the data directory.
YEKE_PUBLIC_URLIf you'll add a cluster The absolute address at which core is reached from the outside; the agent install manifest uses this address. Left empty, core boots fine and login works; only adding a cluster in agent mode stops, with PUBLIC_URL_NOT_CONFIGURED. Core does not guess this address: most installs listen on 0.0.0.0:8080, and that address is not reachable from inside the cluster.
YEKE_TLS_CERT_FILEFor HTTPS Path to the server certificate (plus any intermediates, full chain). See Installing with HTTPS. Left empty, YEKE listens plain http; set one without the other and it won't start.
YEKE_TLS_KEY_FILEFor HTTPS Path to the unencrypted PEM key file. Left empty, YEKE listens plain http; set one without the other and it won't start.
YEKE_ALLOWED_ORIGINSNo Extra origins accepted by the CSRF/Origin check (comma-separated). Needed only when you put a reverse proxy (Cloudflare, nginx, Traefik) in front of core and that layer presents an external address different from the Host core sees; not needed with native TLS, since Host is already the external address.
YEKE_LICENSE_PATHNo Path to the Enterprise license file. Left empty, the install runs as Community: a cap of 3 clusters / 5 users, every core capability unlocked, never expires. Set, it becomes that boot's only license source and the license can no longer be uploaded from the UI.
YEKE_BOOTSTRAP_TOKENNo A fixed setup token for the first administrator: saves you from reading the log if you're scripting the install. Left empty, core generates one at random and logs it.

You don't need to define any YEKE_* variable that isn't in this table. Three of them — YEKE_ALLOW_HEADER_IDENTITY, YEKE_DEV_USER, YEKE_DEV_GROUPS — were removed, and if defined core doesn't start at all: authentication exists precisely because those three were shut off, and silently ignoring them would let you believe it "still works the old way."

Governance settings (Enterprise only)

Enterprise governance uses four additional settings. YEKE_POLICY_MODE selects file (default) or central. Central mode requires policy-central; an unknown mode prevents startup.

Central mode also requires YEKE_POLICY_PACKAGE_PUBLIC_KEYS: the Ed25519 verification keys for policy packages, separate from license and air-gap keys.

YEKE_EXEC_RECORDING_RETENTION_DAYS controls full terminal recording retention (default: 30 days), independently of operation retention.

YEKE_SECRET_DISPLAY controls how visible Secret values are through YEKE: besides the default full, the restricted masked/hidden modes require the secret-display flag and apply to everyone, owner included. See Governance for details, in particular the Secret display section.

What comes next

YEKE is running, but it cannot see a single cluster yet.

Upgrades and backups

An upgrade recreates the container; data and key stay in the volume, so nothing else moves.

docker pull nairotech/yeke:<new-version>
docker rm -f yeke
# then the same run command, with only the tag changed
  • An upgrade is an outage, every single one. The database is single-writer SQLite: the new core can't start until the old one has stopped. The measured window in production is about 60 seconds. Even a release that changes one line of UI text is subject to this outage; the UI ships inside core's image.
  • Migrations run at startup and core prints a schema vN line to its log; confirm success from that line, not by guessing.
  • Backup — the contents of the volume, plus /etc/yeke if you moved the key outside. Take the backup with core stopped: SQLite's -wal file is written while it runs, and a half-copied backup is quietly incomplete.
docker stop yeke
docker run --rm -v yeke-data:/v -v "$PWD":/yedek alpine:3.20 tar czf /yedek/yeke-data.tgz -C /v .
docker start yeke

The archive carries the file ownership (65532) with it; restoring is just the same command in reverse, and core boots straight off the restored volume. The target has to be an empty volume — on a new machine, Docker creates it fresh when you run this command. If you mounted your own directory instead, skip both of these: copying the directory is enough.

docker run --rm -v yeke-data:/v -v "$PWD":/yedek alpine:3.20 tar xzf /yedek/yeke-data.tgz -C /v

If you forgot your password

There is no in-UI recovery when the one administrator forgets their password, or their account gets locked, and nobody else can sign in. Anyone with exec access to the container can issue a new password setup token from the command line.

An operator with container access runs whichever of these three matches their setup. Core does not need to be stopped.

docker compose exec core node tools/yeke-reset-password.mjs admin
docker exec yeke node tools/yeke-reset-password.mjs admin
kubectl -n yeke-system exec deploy/yeke-core -- node tools/yeke-reset-password.mjs admin

The tool does not pick a password: it prints a one-time password setup token, valid for 60 minutes, to the terminal. The user pastes that token into /set-password and chooses their own password. Generating a new token invalidates the previous one.

If the authenticator app is gone too, add --reset-mfa; it clears the MFA enrolment and recovery codes along with the token, and the user can set MFA up again after signing in.

This only works for local, active accounts. An LDAP/OIDC account's password is reset in the directory, not with this tool; a disabled account has to be re-enabled by an administrator first. The operation is written to the audit log.

Anyone who can run this already has exec access to the container's data directory and database — this does not open a new privilege. No password ever reaches the terminal; only the token does. The API rule is unchanged: the setup owner's password still cannot be reset by another user from the UI.

Compose and Kubernetes

Use Docker Compose or Kubernetes to keep the service definition in a file. Both run the same YEKE image.

Docker Compose

No need to clone a repo; this is the whole file:

services:
  core:
    image: nairotech/yeke:0.61.6
    ports:
      - "8080:8080"
    environment:
      YEKE_PUBLIC_URL: ${YEKE_PUBLIC_URL:-}
    volumes:
      - yeke-data:/var/lib/yeke
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 10s
    restart: unless-stopped

volumes:
  yeke-data:
    name: yeke-data

Run docker compose up -d. The name line keeps the volume from getting a project prefix added to its name; the backup commands above apply here too, unchanged. On this path docker ps does show (healthy); the healthcheck is part of this file, not the image.

For HTTPS, change ports to "443:8443" and add the certificate volume and the two TLS environment variables from Installing with HTTPS; the healthcheck stays as is.

Kubernetes

Running core inside a cluster is supported and a manifest set is ready: namespace, ReadWriteOnce PVC, a single Deployment (strategy: Recreate), Service, and a standard Ingress. There we do recommend putting the key in a Secret — the opposite of the single-machine advice: a backup that snapshots the PVC would otherwise carry the key with it and make the encryption pointless. The set is short but has a few fields you'll need to adapt to your setup (hostname, StorageClass, Ingress class); ask and we'll send them.

You don't have to install inside the cluster it manages, and usually you don't want to: when the cluster YEKE manages goes down, the UI that would fix it needs to still be up.

Stuck on a step? Write to us.

Every command on this page was actually run while it was written. If a step still does not go as expected, reach out directly.