Configuration reference
Every hopskip-server setting, its environment variable, and its default.
Every hopskip-server setting. Each one has an environment variable (the
canonical name) and a matching key in the optional TOML config file
passed with --config. An environment variable always wins over the
file.
Validate a deployment’s configuration without starting it:
hopskip-server check-config --config /etc/hopskip/server.toml
Server
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
server.bind | HOPSKIP_SERVER_BIND | host:port | 127.0.0.1:50051 | gRPC bind address serving clients and workers. |
server.internal_bind | HOPSKIP_INTERNAL_BIND | host:port | unset | Security-relevant. Second gRPC bind serving the same API to callers inside the deployment. Set = an mTLS listener is opened, which requires internal_tls.cert_path, internal_tls.key_path and internal_tls.client_ca_path. Unset = one listener, unchanged. |
server.ops_bind | HOPSKIP_OPS_BIND | host:port | unset | Health/metrics surface (/healthz, /readyz, /metrics). Unset = off. |
server.console_bind | HOPSKIP_CONSOLE_API_BIND | host:port | unset | Console HTTP/JSON API bind address. Unset = off. |
server.console_auth | HOPSKIP_CONSOLE_API_AUTH | one of: on, off | on | Security-relevant. Set to off to serve the console API unauthenticated (loopback development only). |
server.access_sync_ms | HOPSKIP_ACCESS_SYNC_MS | integer | 1000 | Security-relevant. How often a node re-reads the access-control journals (user directory, ACL grants) for records another node wrote - i.e. the worst-case delay before a revocation made on one node is enforced here. Only runs under a replicated log store (raft/sharded), where more than one process writes them. 0 disables it, leaving this node’s tables stale until it restarts. |
server.scim_bind | HOPSKIP_SCIM_BIND | host:port | unset | SCIM 2.0 inbound provisioning endpoint (Okta, Entra ID, …), on its own listener. Unset = off; also requires scim_token. |
server.scim_token | HOPSKIP_SCIM_TOKEN | text | unset | Security-relevant. Shared bearer token an identity provider presents to the SCIM endpoint. Required: with no token the endpoint does not bind, since it writes the user directory. |
server.scim_group_prefix | HOPSKIP_SCIM_GROUP_PREFIX | text | unset | Only pushed groups carrying this prefix map to namespaces, with the prefix stripped (hopskip-checkout -> checkout). Unset = every pushed group maps to its own name. |
server.shard_admin_bind | HOPSKIP_SHARD_ADMIN_BIND | host:port | unset | Sharded-backend admin surface. Unset = off; requires the sharded log store. |
server.shard_admin_token | HOPSKIP_SHARD_ADMIN_TOKEN | text | unset | Security-relevant. Shared token every shard-admin request must present. Set = the surface authenticates (and may bind beyond loopback); unset = loopback-only unless explicitly overridden. |
server.shard_admin_allow_remote | HOPSKIP_SHARD_ADMIN_ALLOW_REMOTE | boolean | false | Security-relevant. Permit a non-loopback shard-admin bind without a token. Without shard_admin_token that surface is unauthenticated and can transfer group leadership; prefer setting the token. |
TLS
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
tls.cert_path | HOPSKIP_TLS_CERT_PATH | path | unset | Security-relevant. PEM server certificate. Set together with tls.key_path to terminate TLS. |
tls.key_path | HOPSKIP_TLS_KEY_PATH | path | unset | Security-relevant. PEM private key for the server certificate. |
tls.client_ca_path | HOPSKIP_TLS_CLIENT_CA_PATH | path | unset | Security-relevant. PEM CA that client certificates must be signed by. Set = mTLS required, for every caller of this listener - including customers. To require certificates from in-cluster callers only, open the internal listener (server.internal_bind) instead. |
internal_tls.cert_path | HOPSKIP_INTERNAL_TLS_CERT_PATH | path | unset | Security-relevant. PEM server certificate for the internal listener. Its names are in-cluster ones, so a private CA issues it. |
internal_tls.key_path | HOPSKIP_INTERNAL_TLS_KEY_PATH | path | unset | Security-relevant. PEM private key for the internal listener’s certificate. |
console_api_tls.cert_path | HOPSKIP_CONSOLE_API_TLS_CERT_PATH | path | unset | Security-relevant. PEM certificate the console API terminates TLS with. Set together with the key; unset leaves it plain HTTP. It serves cross-namespace data and raw SQL, so the hop carrying its bearer token is worth encrypting. |
console_api_tls.key_path | HOPSKIP_CONSOLE_API_TLS_KEY_PATH | path | unset | Security-relevant. PEM private key for the console API’s certificate. |
console_api_tls.client_ca_path | HOPSKIP_CONSOLE_API_TLS_CLIENT_CA_PATH | path | unset | Security-relevant. Optional. PEM CA that client certificates on the console API must be signed by. Set = mTLS on that surface, which requires every caller of it to hold a certificate. |
scim_tls.cert_path | HOPSKIP_SCIM_TLS_CERT_PATH | path | unset | Security-relevant. PEM certificate the SCIM endpoint terminates TLS with. Set together with the key; unset leaves it plain HTTP. It authenticates with a single long-lived static token, which is what identity providers support. |
scim_tls.key_path | HOPSKIP_SCIM_TLS_KEY_PATH | path | unset | Security-relevant. PEM private key for the SCIM endpoint’s certificate. |
scim_tls.client_ca_path | HOPSKIP_SCIM_TLS_CLIENT_CA_PATH | path | unset | Security-relevant. Optional. PEM CA that client certificates on the SCIM endpoint must be signed by. Set = mTLS on that surface, which requires every caller of it to hold a certificate. |
ops_tls.cert_path | HOPSKIP_OPS_TLS_CERT_PATH | path | unset | Security-relevant. PEM certificate the ops surface terminates TLS with. Set together with the key; unset leaves it plain HTTP. Turning this on means probes need scheme HTTPS and scrapes need scheme https; a kubelet cannot present a client certificate, so do not set a client CA here without moving probes to exec. |
ops_tls.key_path | HOPSKIP_OPS_TLS_KEY_PATH | path | unset | Security-relevant. PEM private key for the ops surface’s certificate. |
ops_tls.client_ca_path | HOPSKIP_OPS_TLS_CLIENT_CA_PATH | path | unset | Security-relevant. Optional. PEM CA that client certificates on the ops surface must be signed by. Set = mTLS on that surface, which requires every caller of it to hold a certificate. |
raft_tls.cert_path | HOPSKIP_RAFT_TLS_CERT_PATH | path | unset | Security-relevant. PEM certificate this node presents to Raft peers, and to which peers’ certificates are compared. Needs both server auth and client auth: every node is both ends. Set = the peer transport is mutually authenticated, which every node in the cluster must also be. |
raft_tls.key_path | HOPSKIP_RAFT_TLS_KEY_PATH | path | unset | Security-relevant. PEM private key for the peer certificate. |
raft_tls.client_ca_path | HOPSKIP_RAFT_TLS_CLIENT_CA_PATH | path | unset | Security-relevant. PEM CA that peer certificates must be signed by, checked in both directions. Required whenever raft_tls.cert_path is set. |
raft_tls.peer_name | HOPSKIP_RAFT_TLS_PEER_NAME | text | hopskip-raft-peer.internal | The name a peer’s certificate must carry. One name for the whole cluster: a handshake proves membership of the deployment rather than which member, which is all Raft acts on. |
internal_tls.client_ca_path | HOPSKIP_INTERNAL_TLS_CLIENT_CA_PATH | path | unset | Security-relevant. PEM CA that client certificates on the internal listener must be signed by. Required whenever server.internal_bind is set: an internal listener without it is a second public listener. |
Authentication
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
auth.static_secret | HOPSKIP_STATIC_AUTH_SECRET | text | unset | Security-relevant. HS256 shared secret for bearer tokens. Prefer injecting via the environment or a secret mount rather than a config file. |
auth.dev_allow_insecure_secret | HOPSKIP_DEV_ALLOW_INSECURE_SECRET | boolean | false | Security-relevant. Permit booting on the publicly known development secret. Development only - every token is forgeable. |
auth.oidc_issuer | HOPSKIP_OIDC_ISSUER | text | unset | OIDC issuer URL. Set = authenticate with OIDC instead of the shared secret. |
auth.oidc_audience | HOPSKIP_OIDC_AUDIENCE | text | unset | Expected aud claim. Required when the issuer is set. |
auth.oidc_jwks_url | HOPSKIP_OIDC_JWKS_URL | text | unset | JWKS endpoint for signing keys. Optional: discovered from the issuer’s OIDC discovery document when unset. |
auth.oidc_jwks_refresh_secs | HOPSKIP_OIDC_JWKS_REFRESH_SECS | integer | 300 | How often to re-fetch the JWKS. |
auth.oidc_namespace_claim | HOPSKIP_OIDC_NAMESPACE_CLAIM | text | namespaces | Token claim listing the namespaces a principal may act in. |
auth.oidc_subject_claim | HOPSKIP_OIDC_SUBJECT_CLAIM | text | sub | Token claim that becomes the principal’s subject. Point it at email when the provider’s sub is opaque (Okta’s is 00u...): this string is what grants are keyed by and what the audit trail shows. Falls back to sub when the claim is absent. |
auth.oidc_role_claim | HOPSKIP_OIDC_ROLE_CLAIM | text | unset | Token claim listing the roles or groups a principal holds (groups on Okta/Keycloak, roles on Entra, a namespaced URI on Auth0). Unset means principals carry no roles. Roles authorize nothing in Core - namespace membership still decides that - they are read by policy above it, such as a human-task gate inviting everyone holding DBA. |
auth.worker_authz | HOPSKIP_WORKER_AUTHZ | text | unset | Security-relevant. Task-type grants for worker principals: subject=pattern[,pattern];... (* wildcards; a * subject rule applies to everyone). Set = a worker registration declaring an ungranted task type is refused (default-deny for uncovered subjects); unset = task types unrestricted. Malformed = startup error. |
auth.worker_authz_namespaces | HOPSKIP_WORKER_AUTHZ_NAMESPACES | one of: on, off | off | Security-relevant. Set to on to only accept a worker’s completion (complete/fail/checkpoint) when the worker principal’s namespace memberships include the workflow’s namespace. Requires worker tokens to carry meaningful namespace claims. |
auth.require_encrypted_payloads | HOPSKIP_REQUIRE_ENCRYPTED_PAYLOADS | text | unset | Security-relevant. Comma-separated namespaces that refuse plaintext user payloads (ADR 0071 end-to-end encryption); * covers every namespace. A payload-bearing RPC (StartWorkflow, Signal/Query/UpdateWorkflow, SendChannelMessage) into a listed namespace must arrive sealed (a HOPSKIP-E2EE payload envelope) or it is rejected. A policy tripwire for misconfigured clients, not a cryptographic verification: the server holds no payload keys. |
Console accounts
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
console_accounts.database_url | HOPSKIP_CONSOLE_ACCOUNTS_DB | text | unset | Security-relevant. Postgres URL of the Cloud control plane holding console accounts. Set = first-party signup/login (with TOTP, passkeys, email confirmation, password reset) is served on the console listener under /auth; unset = off, the pre-existing behavior. The database must already be migrated (sqitch --chdir db/console deploy): the server verifies the schema at startup and refuses to start otherwise, rather than creating tables itself. See db/README.md. |
console_accounts.origin | HOPSKIP_CONSOLE_ORIGIN | text | unset | Security-relevant. The console’s external origin (e.g. https://console.hopskip.run). Required with the accounts database: it decides emailed links, the WebAuthn relying-party ID passkeys bind to (changing it strands them), and whether the session cookie is Secure. |
console_accounts.signup | HOPSKIP_CONSOLE_SIGNUP | one of: on, off | on | Self-service signup. off keeps login/reset/2FA working while new accounts come only from provisioning. |
console_accounts.smtp_url | HOPSKIP_CONSOLE_SMTP_URL | text | unset | Security-relevant. SMTP relay for confirmation and reset mail (smtp://user:pass@host:port or smtps://…). Unset = links are written to the server log instead: right for development, a credential leak into the log pipeline in production. |
console_accounts.email_from | HOPSKIP_CONSOLE_EMAIL_FROM | text | Hopskip Cloud <no-reply@hopskip.run> | From header on account mail. |
console_accounts.admin_emails | HOPSKIP_CONSOLE_ADMIN_EMAILS | text | unset | Security-relevant. Comma-separated account emails granted the reserved admin namespace at authentication time - the bootstrap answer to “who administers a fresh install”, applied without a database write. |
console_accounts.session_ttl_hours | HOPSKIP_CONSOLE_SESSION_TTL_HOURS | integer | 168 | Console session lifetime in hours. Absolute, not sliding: a stolen cookie ages out no matter how actively it is used. |
console_accounts.oauth_github_client_id | HOPSKIP_CONSOLE_OAUTH_GITHUB_ID | text | unset | GitHub OAuth app client ID. Set together with the secret = “Continue with GitHub” on the console’s login and signup pages; the app’s authorization callback URL must be <origin>/api/auth/oauth/github/callback. |
console_accounts.oauth_github_client_secret | HOPSKIP_CONSOLE_OAUTH_GITHUB_SECRET | text | unset | Security-relevant. GitHub OAuth app client secret, paired with the client ID. |
console_accounts.oauth_google_client_id | HOPSKIP_CONSOLE_OAUTH_GOOGLE_ID | text | unset | Google OAuth client ID. Set together with the secret = “Continue with Google” on the console’s login and signup pages; the client’s authorized redirect URI must be <origin>/api/auth/oauth/google/callback. |
console_accounts.oauth_google_client_secret | HOPSKIP_CONSOLE_OAUTH_GOOGLE_SECRET | text | unset | Security-relevant. Google OAuth client secret, paired with the client ID. |
console_accounts.billing_url | HOPSKIP_CONSOLE_BILLING_URL | text | unset | The hopskip-billing gateway (e.g. http://hopskip-billing:8099). Set together with the token = onboarding asks a new organization for a payment method; unset = it asks for none and every organization is created waived. Not a Stripe endpoint: the console never holds a payment credential, see below. |
console_accounts.billing_token | HOPSKIP_CONSOLE_BILLING_TOKEN | text | unset | Security-relevant. Shared bearer token this console presents to that gateway, paired with the URL. Generate with openssl rand -hex 32; shorter than 32 characters is refused at the sidecar’s startup. |
Onboarding, organizations, and why there is no Stripe key here
With console_accounts.database_url set, an account is not usable until it
belongs to an organization - the thing that owns a namespace, a payment
method, and a set of people. A new account is sent to /welcome, where it
either creates one (details, a namespace nobody else holds, and a card when
one is configured) or joins an existing one by invitation or by asking an
organization discoverable to its confirmed email domain. Joining needs
neither a namespace nor a card, because the organization already has both.
Card capture crosses a process boundary on purpose. hopskip-server
executes untrusted tenant Wasm and does not depend on hopskip-billing, so
no configuration can put a Stripe key in it (ADR 0050). The two billing
variables above point at the sidecar that does hold one; the token
authenticates this console to that sidecar and cannot charge, refund, or
read a card. Card details are entered on the payment provider’s own hosted
page, never on a page the console serves.
A self-hosted deployment leaves both unset. Onboarding still requires an organization - somebody has to own the namespace - and asks for no payment details.
Product analytics
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
analytics.posthog_api_key | HOPSKIP_POSTHOG_API_KEY | text | unset | PostHog project key (phc_…). Set = server-authoritative console account lifecycle events (signed up, email verified, logged in, credential changed) are reported; unset = nothing is ever sent, the default. Delivery is off the request path and flushed at shutdown; a failure never fails a login. Write-only token, so it is configuration rather than a secret. |
analytics.posthog_url | HOPSKIP_POSTHOG_URL | text | https://us.i.posthog.com | PostHog ingestion origin. https://eu.i.posthog.com for EU cloud, or a self-hosted equivalent. Malformed = startup error when a key is set. |
analytics.enabled | HOPSKIP_ANALYTICS | one of: on, off | on | off disables product analytics even when a PostHog key is configured - the operator opt-out, which wins over the key. The same variable turns the hop CLI’s anonymous usage analytics off. |
Log store
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
log_store.backend | HOPSKIP_LOG_STORE_BACKEND | one of: disk, memory, raft, sharded | disk | Event-log backend. memory is the explicit ephemeral opt-in and loses history on restart. |
log_store.disk_path | HOPSKIP_LOG_STORE_DISK_PATH | path | ./.hop/log | Root directory for the disk backend. |
log_store.shard_ranges | HOPSKIP_SHARD_RANGES | text | unset | Shard range assignment for the sharded backend. |
Raft
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
raft.node_id | HOPSKIP_RAFT_NODE_ID | integer | unset | This node’s raft id. Required for the raft/sharded backends. |
raft.bind_addr | HOPSKIP_RAFT_BIND_ADDR | host:port | unset | Address this node’s raft peer transport listens on. |
raft.members | HOPSKIP_RAFT_MEMBERS | text | unset | Bootstrap-time member list, as id@host:port entries. Steady-state membership lives in the node registry (ADR 0053). |
raft.data_dir | HOPSKIP_RAFT_DATA_DIR | path | unset | Directory for this node’s raft state. |
raft.storage_format | HOPSKIP_RAFT_STORAGE_FORMAT | text | proto | On-disk format under raft.data_dir: proto (length-prefixed, CRC-guarded protobuf frames; compact and cheap to encode), jsonl (human-inspectable reference files), or sqlite (WAL databases; raw-BLOB payloads, no rename dances). A directory keeps its format for life; the formats refuse each other’s directories - a node upgraded over an existing jsonl directory must set this to jsonl to keep reading it. |
raft.heartbeat_ms | HOPSKIP_RAFT_HEARTBEAT_MS | integer | unset | Raft heartbeat interval. |
raft.bootstrap | HOPSKIP_RAFT_BOOTSTRAP | boolean | false | Form the cluster from raft.members if it is not already formed. Idempotent; set on exactly one node. |
raft.advertise_addr | HOPSKIP_RAFT_ADVERTISE_ADDR | text | unset | Address peers dial this node on, advertised when it joins a cluster. Defaults to this node’s own raft.members entry, else its bind address; required when auto-scaling with a 0.0.0.0 bind. |
Cluster
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
cluster.seeds | HOPSKIP_CLUSTER_SEEDS | text | unset | Addresses to join a cluster through: host:port and/or dns:name:port, comma-separated. Any live member works, including a follower. |
cluster.failure_detector | HOPSKIP_CLUSTER_FAILURE_DETECTOR | text | heartbeat | Cluster liveness detector: heartbeat (probe mesh), gossip (SWIM), or off. |
cluster.sync_ms | HOPSKIP_CLUSTER_SYNC_MS | integer | 1000 | How often a node re-reads the node registry to learn new peers. 0 disables. |
cluster.query_addr | HOPSKIP_CLUSTER_QUERY_ADDR | text | unset | This node’s visibility-query endpoint, published into its registry record so cluster queries can reach it. |
cluster.client_addr | HOPSKIP_CLUSTER_CLIENT_ADDR | text | the HOPSKIP_SERVER_BIND address | This node’s client-facing gRPC endpoint as peers should reach it, published into its registry record: a client RPC received by a node that does not service its target is forwarded here (ADR 0070). Set it when the bind address is a wildcard or otherwise unreachable from peers. |
cluster.cold_segments | HOPSKIP_CLUSTER_COLD_SEGMENTS | boolean | false | Snapshots reference cold history by content hash instead of shipping it inline; a catching-up replica fetches it from peers. Enable on every node. |
cluster.hot_tail_records | HOPSKIP_CLUSTER_HOT_TAIL_RECORDS | integer | 4096 | How many of the newest applied records stay inline in a snapshot when cold segments are on. |
cluster.auto_scale | HOPSKIP_CLUSTER_AUTO_SCALE | boolean | false | Run the membership autopilot on the raft backend: nodes join through cluster.seeds as learners, are promoted to voters once caught up, and are evicted after cluster.evict_after_ms of silence. Scaling becomes starting or stopping nodes. |
cluster.evict_after_ms | HOPSKIP_CLUSTER_EVICT_AFTER_MS | integer | 300000 | How long a member must be silent before the autopilot evicts it from membership. 0 disables eviction. A restart shorter than this is never mistaken for a departure. |
cluster.min_voters | HOPSKIP_CLUSTER_MIN_VOTERS | integer | 3 | The voter floor the autopilot never evicts below, so losing quorum margin requires an operator, not an outage. |
Durability
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
durability.seal_threshold | HOPSKIP_SEAL_THRESHOLD | integer | 1000 | Events between event-horizon seals. |
durability.spill_watermark | HOPSKIP_SPILL_WATERMARK | ratio 0.0-1.0 | 0.7 | Channel occupancy ratio at which buffered messages spill to the cold tier. |
durability.group_commit_max_batch | HOPSKIP_GROUP_COMMIT_MAX_BATCH | integer | 500 | Maximum appends coalesced into one group commit. |
durability.group_commit_max_linger_ms | HOPSKIP_GROUP_COMMIT_MAX_LINGER_MS | integer | 5 | How long a group commit waits to accumulate a batch. |
Deployment safety
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
deployment.suspension_tracking | HOPSKIP_SUSPENSION_TRACKING | boolean | false | Track which host-call site each live workflow is suspended at, so hop deploy’s compat gate can downgrade a removed-await finding with zero live blast radius. Off by default: only turn it on once every worker serving these namespaces reports suspensions, because a partially-reported index answers “zero” to sites nobody reported. |
Async activities
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
async_activity.secret | HOPSKIP_ASYNC_ACTIVITY_SECRET | text | unset | Security-relevant. Signing key for the task tokens that let an activity be completed out of band (CompleteActivity/FailActivity). Unset = a random per-process key, so tokens stop verifying across a restart and never verify on another node; set a shared secret on any deployment where an async activity may outlive one process. A separate key from auth.static_secret on purpose - this one signs a credential handed to third-party systems. |
async_activity.heartbeat_ms | HOPSKIP_ASYNC_ACTIVITY_HEARTBEAT_MS | integer | 0 | How long a parked async activity may go without a HeartbeatActivity call before Core reclaims and redispatches it. 0 (the default) = never, because the motivating case is waiting on a human; the attempt’s propagated deadline bounds it either way. |
Tiered storage
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
tiered.object_store | HOPSKIP_TIERED_OBJECT_STORE | one of: local, s3 | local | Cold-tier backend. |
tiered.recover | HOPSKIP_TIERED_RECOVER | boolean | false | Recover cold-tier state from the object store on a fresh node. |
tiered.s3_bucket | HOPSKIP_TIERED_S3_BUCKET | text | unset | S3 bucket for the cold tier. Required when object_store is s3. |
tiered.s3_region | HOPSKIP_TIERED_S3_REGION | text | unset | S3 region. |
tiered.s3_endpoint_url | HOPSKIP_TIERED_S3_ENDPOINT_URL | text | unset | Override endpoint, for S3-compatible stores. |
tiered.s3_force_path_style | HOPSKIP_TIERED_S3_FORCE_PATH_STYLE | boolean | false | Use path-style addressing (needed by most S3-compatible stores). |
Visibility
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
visibility.db_path | HOPSKIP_VISIBILITY_DB_PATH | path | hopskip-visibility.sqlite3 | SQLite file backing the visibility projection. |
visibility.tick_ms | HOPSKIP_VISIBILITY_TICK_MS | integer | 1000 | Projection tick interval. |
visibility.batch_size | HOPSKIP_VISIBILITY_BATCH_SIZE | integer | 500 | Records projected per tick. |
Observability
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
observability.log_format | HOPSKIP_LOG_FORMAT | one of: text, json | text | Log output format. json emits one object per line for a log shipper. |
observability.otlp_endpoint | HOPSKIP_OTLP_ENDPOINT | text | unset | OTLP/gRPC collector to export spans to (e.g. http://collector:4317). Unset = no export. |
observability.otlp_service_name | HOPSKIP_OTLP_SERVICE_NAME | text | hopskip-server | Service name reported to the trace backend. |
observability.audit_log_path | HOPSKIP_AUDIT_LOG_PATH | path | unset | Append-only file receiving the control-plane audit trail (auth denials, admin actions) as JSON lines, independent of RUST_LOG. Unset = audit events flow only to the ordinary log stream. |
observability.slo_config | HOPSKIP_SLO_CONFIG | path | unset | JSON file declaring burn-rate SLOs to evaluate. Unset = no SLO evaluation. |
OpenLineage
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
openlineage.endpoint | HOPSKIP_OPENLINEAGE_ENDPOINT | text | unset | Base URL of an OpenLineage receiver such as Marquez (e.g. http://marquez:5000); events post to {endpoint}/api/v1/lineage. Unset = no lineage export. |
openlineage.api_key | HOPSKIP_OPENLINEAGE_API_KEY | text | unset | Security-relevant. Bearer token sent with every lineage post. Unset = no Authorization header. |
openlineage.file | HOPSKIP_OPENLINEAGE_FILE | path | unset | Append run events to this file as newline-delimited JSON (the OpenLineage file transport). Ignored when an endpoint is also set. |
openlineage.tick_ms | HOPSKIP_OPENLINEAGE_TICK_MS | integer | 5000 | Emitter tick interval. |
openlineage.batch_size | HOPSKIP_OPENLINEAGE_BATCH_SIZE | integer | 500 | Records read per partition per tick. |
Metering
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
metering.enabled | HOPSKIP_METERING | one of: on, off | off | Record this node’s usage in an append-only, hash-chained ledger. Action counts are operational telemetry, not billable units; current Cloud pricing uses provider-specific CPU, RAM, storage, and network rates. Off by default for self-hosted deployments. |
metering.ledger_path | HOPSKIP_METERING_LEDGER_PATH | path | .hop/usage | Directory holding the usage ledger’s append-only, hash-chained files (one per namespace). Deliberately beside the event log rather than inside it: billing records outlive workflow history and must not inherit its retention, restore, or rebalancing lifecycle. |
metering.tick_ms | HOPSKIP_METERING_TICK_MS | integer | 5000 | How often the meter folds new log records into usage buckets. |
metering.batch_size | HOPSKIP_METERING_BATCH_SIZE | integer | 500 | Records read per partition per metering tick. |
metering.plan | HOPSKIP_METERING_PLAN | one of: developer, standard, business, dedicated | standard | Legacy Action-rate plan retained for ledger compatibility. It must not be used to invoice under the current resource-based Cloud pricing model. |
metering.export_path | HOPSKIP_METERING_EXPORT_PATH | path | unset | Append flushed usage records to this file as newline-delimited JSON, for ingestion by an external billing system. Unset = no export; the usage ledger is written either way. |
Placement
| Config key | Environment variable | Type | Default | Description |
|---|---|---|---|---|
placement.reconcile_ms | HOPSKIP_PLACEMENT_RECONCILE_MS | integer | unset | Split/merge reconciler interval. Unset or 0 = off; requires the sharded backend. |
placement.leader_imbalance | HOPSKIP_PLACEMENT_LEADER_IMBALANCE | integer | 2 | How far the busiest node’s leadership count may exceed the quietest’s before one is handed over. Leadership is the cheap lever: it costs one RPC and moves no data, and it is what fixes a write-path hotspot. 0 disables it. Values below 2 are raised to 2, since moving one leadership can only close a gap of two or more. |
placement.replica_imbalance | HOPSKIP_PLACEMENT_REPLICA_IMBALANCE | integer | 0 | How far the busiest node’s replica count may exceed the quietest’s before a replica is moved. Off by default: a move is O(total history), so a deployment should opt into paying for it rather than discover it. Values below 2 are raised to 2. |
placement.replication_factor | HOPSKIP_PLACEMENT_REPLICATION_FACTOR | integer | 3 | How many voters each shard group should have. The reconciler adds a replica on a registered node when a group falls below this, which is what makes adding a node fill it. 0 disables the repair and leaves placement entirely operator-driven. |
placement.max_concurrent | HOPSKIP_PLACEMENT_MAX_CONCURRENT | integer | 1 | Concurrent split/merge operations. |
placement.split_write_rate | HOPSKIP_PLACEMENT_SPLIT_WRITE_RATE | integer | unset | Write rate above which a shard is a split candidate. |
placement.split_open_partitions | HOPSKIP_PLACEMENT_SPLIT_OPEN_PARTITIONS | integer | unset | Open-partition count above which a shard is a split candidate. |
placement.merge_write_rate | HOPSKIP_PLACEMENT_MERGE_WRITE_RATE | integer | unset | Write rate below which adjacent shards are merge candidates. |
Example
[server]
# bind = "127.0.0.1:50051"
# internal_bind =
# ops_bind =
# console_bind =
# console_auth = "on"
# access_sync_ms = 1000
# scim_bind =
# scim_token =
# scim_group_prefix =
# shard_admin_bind =
# shard_admin_token =
# shard_admin_allow_remote = false
[tls]
# cert_path =
# key_path =
# client_ca_path =
# cert_path =
# key_path =
# cert_path =
# key_path =
# client_ca_path =
# cert_path =
# key_path =
# client_ca_path =
# cert_path =
# key_path =
# client_ca_path =
# cert_path =
# key_path =
# client_ca_path =
# peer_name = "hopskip-raft-peer.internal"
# client_ca_path =
[auth]
# static_secret =
# dev_allow_insecure_secret = false
# oidc_issuer =
# oidc_audience =
# oidc_jwks_url =
# oidc_jwks_refresh_secs = 300
# oidc_namespace_claim = "namespaces"
# oidc_subject_claim = "sub"
# oidc_role_claim =
# worker_authz =
# worker_authz_namespaces = "off"
# require_encrypted_payloads =
[console_accounts]
# database_url =
# origin =
# signup = "on"
# smtp_url =
# email_from = "Hopskip Cloud <no-reply@hopskip.run>"
# admin_emails =
# session_ttl_hours = 168
# oauth_github_client_id =
# oauth_github_client_secret =
# oauth_google_client_id =
# oauth_google_client_secret =
# billing_url =
# billing_token =
[analytics]
# posthog_api_key =
# posthog_url = "https://us.i.posthog.com"
# enabled = "on"
[log_store]
# backend = "disk"
# disk_path = "./.hop/log"
# shard_ranges =
[raft]
# node_id =
# bind_addr =
# members =
# data_dir =
# storage_format = "proto"
# heartbeat_ms =
# bootstrap = false
# advertise_addr =
[cluster]
# seeds =
# failure_detector = "heartbeat"
# sync_ms = 1000
# query_addr =
# client_addr = "the HOPSKIP_SERVER_BIND address"
# cold_segments = false
# hot_tail_records = 4096
# auto_scale = false
# evict_after_ms = 300000
# min_voters = 3
[durability]
# seal_threshold = 1000
# spill_watermark = 0.7
# group_commit_max_batch = 500
# group_commit_max_linger_ms = 5
[deployment]
# suspension_tracking = false
[async_activity]
# secret =
# heartbeat_ms = 0
[tiered]
# object_store = "local"
# recover = false
# s3_bucket =
# s3_region =
# s3_endpoint_url =
# s3_force_path_style = false
[visibility]
# db_path = "hopskip-visibility.sqlite3"
# tick_ms = 1000
# batch_size = 500
[observability]
# log_format = "text"
# otlp_endpoint =
# otlp_service_name = "hopskip-server"
# audit_log_path =
# slo_config =
[openlineage]
# endpoint =
# api_key =
# file =
# tick_ms = 5000
# batch_size = 500
[metering]
# enabled = "off"
# ledger_path = ".hop/usage"
# tick_ms = 5000
# batch_size = 500
# plan = "standard"
# export_path =
[placement]
# reconcile_ms =
# leader_imbalance = 2
# replica_imbalance = 0
# replication_factor = 3
# max_concurrent = 1
# split_write_rate =
# split_open_partitions =
# merge_write_rate =