Composable Configuration

A way of composing configuration values across multiple dimensions and using them in arbitrary applications. Probably not original.


June 13, 2024

I recently found myself trying to manage the configuration of a bunch of utility applications. These apps were extracting data from a database, massaging it, then uploading to to a Graphql server.

I had three databases: local development, staging, and production, and three servers: test, staging and production. I needed to run any of the apps, passing in appropriate URLS and credentials based on the database and server.

It seems that the conventional way is to use config files with approriate names and somehow knit them together. One example of this is .dotenvx, where you could run

$ dotenvx run -f .env.db-staging -f .env.gql-test -- ruby ....

Using a tool such as this adds yet more dependencies and a bit more cognative load to an already complex project. So I went primitive and wrote something that lets me do this using a command such as:

# use staging database and test gql server
$ db-staging gql-test ruby ....
# or the test database and the staging server
$ db-test gql-staging ruby ...

Embarassing Simplicity

db-staging and gql-test are simply shell scripts that set environment variables. In this case I’m using the fish shell, but this’ll likely work in bash and zsh.1


set -x DB_HOST localhost
set -x DB_PORT 3306
set -x DB_NAME pip
set -x DB_USERNAME dave
set -x DB_PASSWORD "secret"

exec $argv

set -x GQL_SERVER  "https://my.server"
set -x GQL_ACCESS_TOKEN  .....

exec $argv

The only special thing is the last line. After the script sets the configuration into environment variables, it calls exec to invoke the next command on the command line. This command will inherit the environment variables just set. If it’s another configuration script, it can set more variables, and then chain to the next command.

For example, when you run

$ db-staging gql-test ruby ....

the db-staging script receives the arguments gql-test ruby .... When it calls exec, the gql-test script is run, receiving ruby ... as an argument. This is our application, and it runs with all the configuation we just set up.

And, when it finishes and returns us to our top-level prompt, the variables are no longer in the environment.


  1. You’ll need to change $argv to whatever your shell uses.↩︎