Features
Each feature below starts with a situation you have probably lived through, then shows the configuration before and after Microconfig. Every example uses the same two services from the quickstart, so you can run it yourself.
The full reference is in the documentation.
Simple Configuration Layout
Real life: A new team ships a new service. Instead of copying a neighbour's 300 lines, they create a folder next to their other services, include what the company agreed on and add only what is unique. With Microconfig, folders are just folders: group them by team, domain or anything else.
How Microconfig solves it
Put each service in its own folder anywhere under components/, grouped by team,
domain or anything else. Folder names are up to you. Different config types of one
service, like application.yaml and values.deploy, live side
by side in its folder.
components
├── payments
│ ├── payment-backend
│ │ ├── application.yaml
│ │ └── values.deploy
│ └── payment-frontend
│ ├── application.yaml
│ └── values.deploy
└── ...
...
Reuse Common Parts
Real life: Your Kafka cluster moved to a new address, and 50 services have it copy-pasted. Someone will miss one. With Microconfig, connection settings live in one shared component: change one file and every service that includes it gets the new address on the next build.
How Microconfig solves it
Move a shared block to its own component and add #include monitoring, logging
to every service that needs it. Service configs keep only what is unique to them,
and an update to the shared block reaches every service on the next build. A
service can still override any included value.
payment-backend
name: payment-backend
server:
port: 8080
context: /api
monitoring:
secure: false
base-path: /monitoring
endpoints: info, health, ready, prometheus
logging:
level:
ROOT: INFO
APP: DEBUG
payment-frontend
name: payment-frontend
payment-backend:
host: http://payment-backend.local
path: /api
monitoring:
secure: true
base-path: /monitoring
endpoints: info, health, ready, prometheus
logging:
level:
ROOT: INFO
APP: DEBUG
payment-backend
name: payment-backend
server:
port: 8080
context: /api
#include monitoring, logging
payment-frontend
name: payment-frontend
payment-backend:
host: http://payment-backend.local
path: /api
#include monitoring, logging
monitoring:
secure: true
monitoring
monitoring:
secure: false
base-path: /monitoring
endpoints: info, health, ready, prometheus
logging
logging:
level:
ROOT: INFO
APP: DEBUG
Reference Values
Real life: The backend moved its API from /api to /api/v2. Nobody told the frontend, and it broke in production. With Microconfig, the frontend references the backend's value instead of copying it, so both change together.
How Microconfig solves it
Reference a value instead of copying it: ${payment-backend@server.context}
reads server.context from the backend's config, ${this@name}
from the current one. The value lives in one place, the dependency is explicit, and
a reference to a missing component or key fails the build.
payment-backend
name: payment-backend
server:
port: 8080
context: /api
...
payment-frontend
name: payment-frontend
payment-backend:
host: http://payment-backend.local
path: ${payment-backend@server.context}
...
payment-frontend
name: payment-frontend
payment-backend:
host: http://payment-backend.local
path: /api
...
Dynamic Values
Real life: timeoutMs: 180000. Is that 3 minutes or 30? With Microconfig, you write #{3 * 60 * 1000} and the next person reading the config doesn't need a calculator.
How Microconfig solves it
Write values as expressions with #{...}: arithmetic like
#{3 * 60 * 1000}, string functions like toUpperCase(), and
placeholders inside them. The generated file gets the plain result.
payment-frontend
name: payment-frontend
payment-backend:
timeoutMs: 180000
log:
filename: PAYMENT-FRONTEND.log
...
payment-frontend
name: payment-frontend
payment-backend:
timeoutMs: #{ 3 * 60 * 1000 }
log:
filename: #{ '${this@name}'.toUpperCase() }.log
...
Environment Specific Config
Real life: A client needs their own stage by tomorrow. Copying 50 service configs and editing database hosts by hand takes a day and invites mistakes. With Microconfig, a new environment is one small file that overrides only what differs.
How Microconfig solves it
Keep the base config in application.yaml and only the differences in
application.dev.yaml or application.prod.yaml next to it.
Microconfig merges them for each environment, and the more specific value wins. A
new environment is one file in envs/ plus overrides where it differs.
payment-backend dev
name: payment-backend
payment-gateway: http://gateway-mock.local
database:
type: Postgres
pool-size: 10
url: jdbc:postgres://10.10.10.10:5432/database
...
payment-backend prod
name: payment-backend
payment-gateway: https://payment-gateway.com
database:
type: Postgres
pool-size: 50
url: jdbc:postgres://20.20.20.20:5432/database
...
payment-backend/application.dev.yaml
payment-gateway: http://gateway-mock.local
database:
host: 10.10.10.10
payment-backend/application.prod.yaml
payment-gateway: https://payment-gateway.com
database:
host: 20.20.20.20
pool-size: 50
payment-backend/application.yaml
name: payment-backend
database:
type: Postgres
pool-size: 10
url: jdbc:postgres://${this@database.host}:5432/database
Static Files Templating
Real life: Every service needs its own logback.xml, and only prod should ship logs to Logstash. Fifty almost-identical XML files drift apart. With Microconfig, you keep one template and each service gets its own version with its name and environment settings filled in.
How Microconfig solves it
Keep one template, for example logback.xml, with placeholders like
${this@name} and Mustache
sections for the parts that only some environments need. Microconfig renders a
separate file for every service and environment.
payment-backend log config dev
<configuration>
<appender class="FileAppender">
<file>logs/payment-backend.log</file>
<encoder>
<pattern>%d{HH:mm:ss} %-5level %logger %msg %n</pattern>
</encoder>
</appender>
</configuration>
payment-backend log config prod
<configuration>
<appender class="FileAppender">
<file>logs/payment-backend.log</file>
<encoder>
<pattern>%d{HH:mm:ss} %-5level %logger %msg %n</pattern>
</encoder>
</appender>
<appender class="LogstashTcpSocketAppender">
<destination>30.30.30.30:9600</destination>
<encoder class="LogstashEncoder">
<customFields>{"servicename":"payment-backend"}</customFields>
</encoder>
</appender>
</configuration>
log template.xml
<configuration>
<appender class="FileAppender">
<file>logs/${this@name}.log</file>
<encoder>
<pattern>%d{HH:mm:ss} %-5level %logger %msg %n</pattern>
</encoder>
</appender>
{{#appender.stash}}
<appender class="LogstashTcpSocketAppender">
<destination>30.30.30.30:9600</destination>
<encoder class="LogstashEncoder">
<customFields>{"servicename":"${this@name}"}</customFields>
</encoder>
</appender>
{{/appender.stash}}
</configuration>
payment-backend prod
mc.mustache.log:
fromFile: ${log@configDir}/template.xml
toFile: logback.xml
name: payment-backend
#var appender.stash: true
...
Config Diff
Real life: It's Friday, the release goes out in an hour, and someone asks: “What changes in prod config this time?” With Microconfig, every build writes a git-style diff next to each service's configs: added, removed and changed properties, ready to review.
How Microconfig solves it
Every build compares the new files with the previous build in the same output folder and
writes diff-application.yaml and similar files next to them: added,
removed and changed properties for each service.
payment-frontend v1
name: payment-frontend
monitoring:
base-path: /monitoring
endpoints: info, health, ready, prometheus
payment-backend:
host: http://payment-backend.local
path: /api
server:
maxThreads: 100
minThreads: 10
port: 80
timeoutMs: 180000
payment-frontend v2
name: payment-frontend
monitoring:
base-path: /monitoring
endpoints: info, health, ready, prometheus
payment-backend:
host: https://payment-backend.local
path: /api
timeoutMs: 180000
server:
port: 80
payment-frontend diff between v1 and v2
+payment-backend:
timeoutMs: 180000
-server:
maxThreads: 100
minThreads: 10
timeoutMs: 180000
payment-backend:
host: http://payment-backend.local -> https://payment-backend.local
More in the documentation
- Environment variables and system properties:
${env@BUILD_NUMBER},${system@user.name} - Default values for references:
${this@dbHost:postgres.local} - Temporary properties with
#varthat feed placeholders but don't reach the result - Removing base properties in a specific environment
- Profiles and explicit environment names for includes and placeholders
- Environment descriptors: groups of services and environments that extend each other
- Copying binary files such as certificates next to the generated configs
Want to see it on your machine? The quickstart takes about 2 minutes: install, build, change one line and see every service update.
Try it in 2 minutes →