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]
| Key | Type | Default | Meaning |
|---|---|---|---|
| root | path | "/var/lib/flint" | store directory, owned by the daemon user |
| max_size | size | "80GiB" | soft ceiling; eviction starts at 95 % of it |
| policy | enum | "lru" | lru or size - evict oldest, or biggest first |
| ttl | duration | "21d" | maximum age regardless of use; "0" disables |
| fsync | enum | "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.
| Key | Type | Default | Meaning |
|---|---|---|---|
| env_allow | list | see below | environment variables folded into the key |
| normalize_paths | bool | true | strip the working directory from the argument vector |
| include_mtime | bool | false | when true, content digests are ignored in favour of timestamps |
| toolchain_probe | enum | "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]
| Key | Type | Default | Meaning |
|---|---|---|---|
| max_entry_size | size | "2GiB" | larger results are refused, never split |
| max_inputs | int | 4096 | guard against a pathological preprocessor |
| write_queue | int | 256 | pending stores before writes are dropped |
| compress | enum | "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
| Key | Applies to | Default | Meaning |
|---|---|---|---|
| kind | both | - | objs or peer; required |
| read_only | both | false | look up but never upload |
| parallel | objs | 8 | concurrent object requests |
| credentials | objs | - | file with key id and secret, mode 0600 enforced |
| token_file | peer | - | shared secret, compared in constant time |
| timeout | both | "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", not80. A bare integer is a parse error. - Durations take
s,m,hordonly. 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_allowreplaces the default list rather than extending it. If you add one variable, repeat the five you still want.
Last updated: 2026-09-28