Skip to content

Klokk Anchor

Klokk Anchor is a companion service that writes a daily, tamper-evident proof of your time-tracking data to the IOTA Rebased (Starfish) blockchain. Each event is reduced to a one-way SHA-256 commitment combined into a Merkle tree; only the daily Merkle root is anchored, so no personal data ever leaves toward the chain. Optionally adds an RFC 3161 trusted timestamp.

Prerequisites: a running Klokk with EXTENSIONS_ENABLED=true; a Klokk API key with the read:time_entries and read:companies scopes; and two 32-byte hex secrets (one for at-rest encryption, one for company-token hashing). For mainnet, a funded IOTA wallet (created in the anchor UI); Klokk’s anchor smart contract is the built-in default.

Image: klokkme/anchor / ghcr.io/klokk-me/anchor (tags stable, latest). PostgreSQL is supported via DATABASE_URL.

docker-compose.yml
services:
klokk-anchor:
image: klokkme/anchor:stable
restart: unless-stopped
ports:
- "127.0.0.1:9090:9090"
volumes:
- anchor-data:/app/data
environment:
ACCEPT_TERMS: "true" # accepts the terms of use: https://klokk.me/terms
KLOKK_BASE_URL: "https://klokk.example.com"
KLOKK_API_KEY: "klokk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
DATABASE_URL: "sqlite:/app/data/klokk-anchor.db"
ENCRYPTION_KEY: "change-me-openssl-rand-hex-32"
COMPANY_TOKEN_PEPPER: "change-me-openssl-rand-hex-32"
IOTA_NETWORK: "mainnet"
TZ: "Europe/Berlin"
TIMEZONE: "Europe/Berlin"
volumes:
anchor-data:
docker-compose.yml
services:
klokk-anchor:
image: klokkme/anchor:stable
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
pids_limit: 200
ports:
- "127.0.0.1:9090:9090"
volumes:
- anchor-data:/app/data
environment:
ACCEPT_TERMS: "true" # accepts the terms of use: https://klokk.me/terms
KLOKK_BASE_URL: "https://klokk.example.com"
KLOKK_API_KEY: "klokk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
DATABASE_URL: "sqlite:/app/data/klokk-anchor.db"
ENCRYPTION_KEY: "change-me-openssl-rand-hex-32"
COMPANY_TOKEN_PEPPER: "change-me-openssl-rand-hex-32"
IOTA_NETWORK: "mainnet"
TZ: "Europe/Berlin"
TIMEZONE: "Europe/Berlin"
volumes:
anchor-data:

Generate the secrets first: run openssl rand -hex 32 twice — for ENCRYPTION_KEY and for COMPANY_TOKEN_PEPPER. Changing ENCRYPTION_KEY after wallets exist makes them permanently unreadable. Every variable can also come from a file (Docker secrets): <NAME>_FILE, for example ENCRYPTION_KEY_FILE.

Variable Default Description
ACCEPT_TERMS Required — Set to true to accept the terms of use. Without it Klokk Anchor logs the link and exits.
KLOKK_BASE_URL Required — Base URL of your Klokk instance.
KLOKK_API_KEY Required — Extension API key with read:time_entries + read:companies. A company-scoped key limits the anchor to that company. Keep secret.
DATABASE_URL sqlite:data/klokk-anchor.db SQLite path (sqlite:…) or PostgreSQL DSN (postgresql://…).
ENCRYPTION_KEY Required — 64-char hex (32 bytes). AES-256-GCM key for stored wallet private keys. openssl rand -hex 32.
COMPANY_TOKEN_PEPPER Required — Secret mixed into company tokens before SHA-256 hashing, so company IDs cannot be reversed from on-chain values.
IOTA_NETWORK mainnet mainnet, testnet or localnet. IOTA_ANCHOR_PACKAGE defaults to Klokk’s deployed package for the selected network.
TIMEZONE / DAILY_BOOKING_TIME UTC / 00:00 IANA timezone and fixed daily booking time (HH:MM) for anchoring the previous day.
ANCHOR_EMPTY_DAYS true Anchor a proof even on days with no entries (positive “nobody worked” evidence). One transaction per company per empty day.
TSA_URL https://freetsa.org/tsr RFC 3161 timestamping authority; only the daily Merkle root is sent to it. The default FreeTSA is not a qualified eIDAS service — for that, set a qualified QTSP. Empty (TSA_URL=) turns timestamping off. Best-effort; never blocks anchoring.
LEDGER_URLS — Comma-separated Klokk Ledger URLs to cross-check proofs during verification — an operator-independent fourth check.
ERROR_REPORTS — Fixes whether this installation shares anonymised error reports with the Klokk team. Unset: superadmins decide under Error log (off until switched on). true: always on. false: off, and Report a bug and Suggest a feature are disabled too. What is sent: see the privacy policy.
ERROR_REPORTS_URL https://radar.klokk.me Klokk Radar: where error reports, bug reports and feature requests go, and where the update notice reads the newest version.
UPDATE_CHECK true Twice a day, reads the newest Klokk Anchor version from Klokk Radar’s public release list and shows superadmins an update notice. false turns it off.
ADMIN_PORT 9090 Port for the web UI and REST API (bind behind a reverse proxy for remote access).

Klokk Anchor can notify you via ntfy, a webhook or e-mail when anchoring fails, a submitted proof is not confirmed (or its on-chain root does not match), Klokk cannot be read during a booking, or a wallet balance falls below WALLET_LOW_BALANCE_MIST. Problems of the same kind are combined into one message (at most one every 15 minutes), so a catch-up over many days does not flood you. Superadmins can send a test notification from the dashboard, or run klokk-anchor --test-notification in the container.

Variable Default Description
NOTIFY_NTFY_URL — ntfy topic URL, e.g. https://ntfy.sh/my-anchor or your own server.
NOTIFY_NTFY_TOKEN — ntfy access token for protected topics.
NOTIFY_NTFY_USER / _PASSWORD — Alternatively: ntfy user and password.
NOTIFY_WEBHOOK_URL — Receives the same notifications as a JSON POST (event, title, message, priority, count, url).
NOTIFY_WEBHOOK_SECRET — Signs the webhook body: header X-Klokk-Anchor-Signature: sha256=<hex HMAC-SHA256>.
NOTIFY_SMTP_HOST — Mail server; switches e-mail notifications on.
NOTIFY_SMTP_FROM — Sender, e.g. klokk-anchor <[email protected]> (required for e-mail).
NOTIFY_SMTP_TO — Recipients, comma-separated (required for e-mail).
NOTIFY_SMTP_USER / _PASSWORD — Sign-in at the mail server; only over an encrypted connection.
NOTIFY_SMTP_SECURITY starttls starttls (port 587), tls (465) or none (25, only for a relay in the local network, without sign-in).
NOTIFY_SMTP_PORT — Overrides the port that follows from NOTIFY_SMTP_SECURITY.
NOTIFY_EVENTS all Comma-separated selection of anchor_failed, proof_failed, klokk_unreachable, wallet_low_balance.
NOTIFY_LANGUAGE en Language of the messages: en or de.
NOTIFY_NAME klokk-anchor Prefix of every title, to tell several instances apart.
WALLET_LOW_BALANCE_MIST 100000000 Low-balance threshold in MIST (1 IOTA = 10⁹ MIST); one notification per drop below it.
PUBLIC_BASE_URL — The address of the interface as the browser opens it, e.g. https://anchor.example.com. Passkeys belong to this address (relying-party id and origin, set apart with WEBAUTHN_RPID and WEBAUTHN_ORIGIN); without it passkeys can only be added at localhost. Notifications link to this address.