Kura
Kura is Tuist's decentralized cache mesh for build artifacts and cache metadata. It lets you place cache nodes close to the machines that produce and consume build outputs, whether those machines run in CI, developer offices, remote workstations, or regional compute clusters.
The goal is low-latency caching everywhere, not only in the one environment where a central cache happens to be nearby. Each Kura node serves reads and writes from local disk, while the mesh replicates artifacts and metadata between peers so other locations can benefit from the same cache over time.
How Kura fits with Tuist#
The Tuist server tells clients which Kura endpoints to use. This keeps endpoint discovery centralized while allowing the cache itself to stay decentralized and close to the compute that needs it.
Kura is the data plane. It serves cache reads and writes, stores local state on disk, and replicates artifacts and metadata to peer nodes.
Deploy on Kubernetes#
Kura is distributed as a Helm chart through GitHub Container Registry. It deploys Kura as a StatefulSet with persistent volumes, a headless service for peer discovery, and a regular service for HTTP and gRPC traffic.
helm upgrade --install kura oci://ghcr.io/tuist/charts/kura \
--namespace kura \
--create-namespace \
--version <version> \
--set image.tag=<tag> \
--set config.region=local
For a self-hosted Tuist server running in the same cluster, expose the Kura service through the Tuist chart:
server:
kuraEndpointUrls:
- http://kura.kura.svc.cluster.local:4000
This renders TUIST_KURA_ENDPOINTS in the server pod. When clients request the Kura cache technology, the server returns those endpoints.
Every Kura node must own its own KURA_DATA_DIR. Kura takes an application-level writer lock on the data directory and expects exactly one process to own it. In Kubernetes, use one persistent volume per pod. Outside Kubernetes, do not point multiple processes at the same mounted directory.
Deploy without Kubernetes#
Kura can also run as a regular container on VMs or bare-metal hosts. In this mode, you are responsible for process supervision, persistent storage, routing, and peer discovery.
At minimum, each node needs a persistent data directory, a temporary directory, a public HTTP port, an internal peer URL, and either a static peer list or a discovery mechanism:
docker run -d --name kura \
-p 4000:4000 \
-p 50051:50051 \
-p 7443:7443 \
-v /var/lib/kura:/var/cache/kura \
-e KURA_PORT=4000 \
-e KURA_GRPC_PORT=50051 \
-e KURA_INTERNAL_PORT=7443 \
-e KURA_TENANT_ID=default \
-e KURA_REGION=local \
-e KURA_TMP_DIR=/tmp/kura \
-e KURA_DATA_DIR=/var/cache/kura \
-e KURA_NODE_URL=http://kura-1.internal:7443 \
-e KURA_PEERS=http://kura-1.internal:7443,http://kura-2.internal:7443 \
ghcr.io/tuist/kura:<tag>
Then configure the Tuist server with the URLs that clients can reach:
TUIST_KURA_ENDPOINTS=https://kura-1.example.com,https://kura-2.example.com
Configuration#
The Helm chart renders the common runtime settings from values.yaml. If you run Kura without Kubernetes, set the same variables directly on the process. Variables that the chart does not map directly can be injected through extraEnv or extraEnvFrom.
| Environment variable | Description | Required | Default | Helm value |
|---|---|---|---|---|
KURA_PORT |
Public HTTP port for cache traffic and health endpoints. | Yes | No default | service.httpPort |
KURA_GRPC_PORT |
gRPC port for Bazel and Buck2 REAPI traffic. | Yes | No default | service.grpcPort |
KURA_INTERNAL_PORT |
Internal HTTP or mTLS port used by Kura peers. | Yes | No default | peerTls.internalPort |
KURA_TENANT_ID |
Default tenant identifier for the node. | Yes | No default | config.tenantId |
KURA_REGION |
Region label used in metrics and replication state. | Yes | No default | config.region |
KURA_TMP_DIR |
Temporary directory for staged request bodies and multipart assembly. | Yes | No default | Fixed to /tmp/kura |
KURA_DATA_DIR |
Persistent directory for metadata state and segment files. | Yes | No default | Fixed to /var/cache/kura |
KURA_NODE_URL |
Canonical internal URL other peers use to reach this node. | Yes | No default | Derived from the pod DNS name and peerTls.internalPort |
KURA_PEERS |
Seed peer list used before discovery converges. | No | KURA_NODE_URL |
Derived from the StatefulSet replicas |
KURA_DISCOVERY_DNS_NAME |
DNS name used for automatic peer discovery. | No | Disabled | Enabled by config.discovery.enabled |
KURA_INTERNAL_TLS_CA_CERT_PATH |
CA certificate used to verify peer mTLS. | No | Disabled | peerTls.enabled and peerTls.caCertFileName |
KURA_INTERNAL_TLS_CERT_PATH |
Certificate used by the internal peer mTLS listener. | No | Disabled | peerTls.enabled and peerTls.certFileName |
KURA_INTERNAL_TLS_KEY_PATH |
Private key used by the internal peer mTLS listener. | No | Disabled | peerTls.enabled and peerTls.keyFileName |
KURA_GRPC_TLS_CERT_PATH |
Certificate used to terminate TLS on the public gRPC listener. | No | Disabled | extraEnv |
KURA_GRPC_TLS_KEY_PATH |
Private key paired with KURA_GRPC_TLS_CERT_PATH. |
No | Disabled | extraEnv |
KURA_FILE_DESCRIPTOR_POOL_SIZE |
File-descriptor budget for request and background I/O. | No | Auto-derived | config.fileDescriptors.poolSize |
KURA_FILE_DESCRIPTOR_ACQUIRE_TIMEOUT_MS |
How long a request waits before FD backpressure fails the checkout. | No | 5000 |
config.fileDescriptors.acquireTimeoutMs |
KURA_SEGMENT_HANDLE_CACHE_SIZE |
Maximum number of pinned segment read handles. | No | Auto-derived | config.fileDescriptors.segmentHandleCacheSize |
KURA_DRAIN_COMPLETION_TIMEOUT_MS |
Grace window for in-flight HTTP and gRPC work during shutdown. | No | 240000 |
config.shutdown.drainCompletionTimeoutMs |
KURA_MEMORY_SOFT_LIMIT_BYTES |
Soft memory watermark where Kura starts reducing optional memory use. | No | Auto-derived | config.memory.softLimitBytes |
KURA_MEMORY_HARD_LIMIT_BYTES |
Hard memory watermark where Kura pauses replication and trims hot caches. | No | Auto-derived | config.memory.hardLimitBytes |
KURA_MANIFEST_CACHE_MAX_BYTES |
Maximum size of the in-memory manifest cache. | No | Auto-derived | config.memory.manifestCacheMaxBytes |
KURA_MAX_KEYVALUE_BYTES |
Maximum per-request keyvalue payload size. | No | 1048576 |
config.memory.maxKeyvalueBytes |
KURA_METADATA_STORE_MAX_OPEN_FILES |
File descriptor budget reserved for the metadata store. | No | Auto-derived | config.metadataStore.maxOpenFiles |
KURA_METADATA_STORE_MAX_BACKGROUND_JOBS |
Background flush and compaction concurrency for the metadata store. | No | Auto-derived | config.metadataStore.maxBackgroundJobs |
KURA_METADATA_STORE_READ_CACHE_BYTES |
Capacity of the metadata-store read cache. | No | Auto-derived | extraEnv |
KURA_METADATA_STORE_WRITE_BUFFER_POOL_BYTES |
Total memory budget reserved for metadata write buffering. | No | Auto-derived | extraEnv |
KURA_METADATA_STORE_WRITE_BUFFER_BYTES |
Size of each metadata write buffer before flush. | No | Auto-derived | extraEnv |
KURA_METADATA_STORE_MAX_WRITE_BUFFERS |
Maximum number of metadata write buffers kept in memory. | No | Auto-derived | extraEnv |
KURA_OUTBOX_MAX_DEPTH |
Maximum replication outbox depth before public writes return backpressure. | No | 100000 |
extraEnv |
KURA_MULTIPART_UPLOAD_TTL_MS |
How long an in-progress multipart upload may sit before expiring. | No | 86400000 |
extraEnv |
KURA_MULTIPART_JANITOR_INTERVAL_MS |
How often Kura scans for stale multipart uploads. | No | 600000 |
extraEnv |
KURA_BOOTSTRAP_TIMEOUT_MS |
Maximum time a single bootstrap-from-peer task may run. | No | 1800000 |
extraEnv |
KURA_BOOTSTRAP_MAX_CONCURRENT_PEERS |
Maximum concurrent bootstrap-from-peer tasks. | No | 8 |
extraEnv |
KURA_TOKIO_WORKER_THREADS |
Number of Tokio worker threads. | No | Auto-derived | extraEnv |
KURA_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
OTLP traces endpoint. Leave empty to disable tracing. | No | Disabled | config.telemetry.otlpTracesEndpoint |
KURA_OTEL_SERVICE_NAME |
OpenTelemetry service name. | Yes | No default | Pod name in Helm |
KURA_OTEL_DEPLOYMENT_ENVIRONMENT |
OpenTelemetry deployment environment. | Yes | No default | config.telemetry.deploymentEnvironment |
KURA_SENTRY_DSN |
Sentry DSN for panic and error reporting. | No | Disabled | extraEnv or extraEnvFrom |
KURA_GEOIP_REFRESH_INTERVAL_SECS |
Interval for refreshing the vendored GeoIP database. Set 0 to disable refreshes. |
No | 86400 |
config.geoip.refreshIntervalSecs |
KURA_EXTENSION_ENABLED |
Enables Lua extension hooks. | No | Disabled | extension.enabled |
KURA_EXTENSION_SCRIPT_PATH |
Path to the Lua extension script. | Required when extensions are enabled | No default | Derived from extension.mountDir and extension.scriptFileName |
KURA_EXTENSION_HOOK_TIMEOUT_MS |
Timeout for each extension hook invocation. | No | 25 |
extraEnv |
KURA_EXTENSION_AUTH_CACHE_ALLOW_TTL_SECONDS |
TTL for positive extension authentication and authorization cache entries. | No | 600 |
extraEnv |
KURA_EXTENSION_AUTH_CACHE_DENY_TTL_SECONDS |
TTL for negative extension authentication and authorization cache entries. | No | 3 |
extraEnv |
KURA_EXTENSION_FAIL_CLOSED_AUTHENTICATE |
Whether authentication hook errors reject the request. | No | true |
extraEnv |
KURA_EXTENSION_FAIL_CLOSED_AUTHORIZE |
Whether authorization hook errors reject the request. | No | true |
extraEnv |
KURA_EXTENSION_FAIL_OPEN_RESPONSE_HEADERS |
Whether response-header hook errors are ignored. | No | true |
extraEnv |
KURA_EXTENSION_CACHE_MAX_ENTRIES |
Maximum entries kept in each extension cache. | No | 100000 |
extraEnv |
If you enable internal peer mTLS, set KURA_INTERNAL_TLS_CA_CERT_PATH, KURA_INTERNAL_TLS_CERT_PATH, and KURA_INTERNAL_TLS_KEY_PATH together. KURA_NODE_URL and every value in KURA_PEERS must then use https:// with the internal peer port.