The question behind every setting
With a config server, it is tempting to put everything there. Without one, it is tempting to put everything in the application file. Both lead to the same trouble: the same value in two places, and nobody sure which one is in force.
We sort every setting into exactly one home, by asking one question: would an operator ever want a different value in one environment, or want to change it without a rebuild?
The sorting rule
- Operational values, such as limits, lifetimes, page sizes and feature flags, live in the config repository, in a development folder and a live folder. There is no fallback in the jar or in the code, so a missing key fails the start-up instead of quietly using a guess.
- Wiring, such as ports, the shape of the datasource and the changelog path, lives in the application's own file.
- Overrides for a developer's machine live in a local profile file inside the application, and never in the config repository.
- Test values live in the test configuration, which imports the development file so tests use the same numbers as the development stack.
- Secrets come from the environment or a secret store, never from git.
The development and live files of the same name are independent copies. A change goes into both in the same change, or the commit explains why they differ.
Do not trust the documentation for precedence
Which source wins when a key appears in more than one place? Documentation tells you the intent. We tested it with a real value instead. When the same limit was set in the jar and on the config server, the config server won. When it was set in the local profile file and on the server, the local file won.
That result is exactly what we wanted, because a tunable moved to the config server cannot be hidden by an old value in the jar, and a developer's local override still works. But we only rely on it because we saw it happen.
The trap that hid our metrics
A shared baseline file on the config server set which management endpoints are exposed, and it listed only health and info. Config server values beat a service's own file, so the metrics and Prometheus endpoints that each service declared locally were silently switched off.
The symptom was confusing: depending on the service, the metrics endpoint answered with a 401, a 404 or a 500, never with a clear message that it was not exposed. Because the local file looked correct, it took a while to look in the right place. The fix was a small per-service file that restates the full list. It is now a step on the checklist for every new service.
The lesson is general. A shared baseline is convenient, but it can override a service's own correct setting without a sound. When behaviour surprises you, check which source actually won.