flintstackbuild-cache daemon · a hosted project
Project status: activeLicense: Apache-2.0Latest: 0.11.3 (2026-09-28)releases feed

Flintstack › Configuration

Configuration

One file, five tables, TOML syntax. The daemon reads it at startup and on SIGHUP; the client reads only the [client] keys from it. Unknown keys are a hard error, not a warning - a typo in a cache configuration is too expensive to ignore.

[store]

KeyTypeDefaultMeaning
rootpath"/var/lib/flint"store directory, owned by the daemon user
max_sizesize"80GiB"soft ceiling; eviction starts at 95 % of it
policyenum"lru"lru or size - evict oldest, or biggest first
ttlduration"21d"maximum age regardless of use; "0" disables
fsyncenum"entry"none, entry or strict

fsync = "entry" flushes each finished object but not the directory, which is the right trade on a builder: a crash can lose the last few entries, never an earlier one. strict costs roughly 8 % of write throughput on our hardware and exists for people who share one store between machines that reboot unpredictably.

[key]

This table decides what counts as "the same build". Changing anything here invalidates every existing entry, so treat it as a schema, not as a tuning knob.

KeyTypeDefaultMeaning
env_allowlistsee belowenvironment variables folded into the key
normalize_pathsbooltruestrip the working directory from the argument vector
include_mtimeboolfalsewhen true, content digests are ignored in favour of timestamps
toolchain_probeenum"version"version, digest or path
[key]
env_allow = ["CC", "CXX", "CFLAGS", "CXXFLAGS", "LDFLAGS", "SOURCE_DATE_EPOCH"]
toolchain_probe = "digest"

toolchain_probe = "digest" hashes the compiler binary itself instead of trusting its version string. It is slower by one read of a 100 MB file per daemon lifetime and it is the only setting that survives a distribution patching the compiler without bumping the version - which is a thing that happens, and which silently serves stale objects under "version".

include_mtime is a footgun. It exists because one large user has a generator that rewrites identical files on every run and needed the old behaviour. With it on, a touch invalidates the world and a restored file with an old timestamp can produce a false hit. Leave it false.

[limits]

KeyTypeDefaultMeaning
max_entry_sizesize"2GiB"larger results are refused, never split
max_inputsint4096guard against a pathological preprocessor
write_queueint256pending stores before writes are dropped
compressenum"zstd-3"none or zstd-N for N in 1–9

A dropped write is logged at warning level and never breaks a build: the compiler has already produced the real output by then. If the warning appears in bursts, the disk is the bottleneck, and raising write_queue only moves the queue into memory.

[remote]

Zero or more backends, tried in the order they appear. The local fs tier is implicit and always first.

[[remote]]
kind = "objs"
endpoint = "objects.internal.example:443"
bucket = "ci-build-cache"
region = "eu-central"
credentials = "/etc/flint/objs.creds"
read_only = false
parallel = 8

[[remote]]
kind = "peer"
address = "cache-b.rack7.internal:7914"
token_file = "/etc/flint/token"
read_only = true
KeyApplies toDefaultMeaning
kindboth-objs or peer; required
read_onlybothfalselook up but never upload
parallelobjs8concurrent object requests
credentialsobjs-file with key id and secret, mode 0600 enforced
token_filepeer-shared secret, compared in constant time
timeoutboth"3s"per-request; a timeout counts as a miss

[log]

[log]
level = "info"      # error, warn, info, debug
format = "text"     # or "json"
slow_ms = 250       # log any store operation slower than this

At debug the daemon prints one line per key component, which is useful for about ten minutes and ruinous for a disk after that. slow_ms is the setting worth keeping on in production: a cache that got slower is nearly always a disk that got slower, and this is how you find out before anybody files a ticket.

Full example

# /etc/flint/flint.toml - single builder, shared read-only upstream
[store]
root = "/var/lib/flint"
max_size = "120GiB"
policy = "lru"
ttl = "30d"

[key]
env_allow = ["CC", "CXX", "CFLAGS", "CXXFLAGS", "SOURCE_DATE_EPOCH"]
toolchain_probe = "digest"

[limits]
max_entry_size = "1GiB"
compress = "zstd-5"

[[remote]]
kind = "objs"
endpoint = "objects.internal.example:443"
bucket = "ci-build-cache"
credentials = "/etc/flint/objs.creds"
read_only = true

[log]
level = "info"
slow_ms = 250

Validate it without starting the service: flintd --config /etc/flint/flint.toml --dry-run parses the file, checks the store and exits 0 if the daemon would have come up.

Pitfalls

  • Sizes are binary and must carry a unit: "80GiB", not 80. A bare integer is a parse error.
  • Durations take s, m, h or d only. There is no week suffix, and "3w" will stop the daemon at startup.
  • A single-bracket [remote] instead of double-bracket [[remote]] defines a table, not an array element, and the backend is silently not a list any more - this is the one error message people still mail us about.
  • env_allow replaces the default list rather than extending it. If you add one variable, repeat the five you still want.

Last updated: 2026-09-28