Ilija Matoski

    ← Articles

    Which env wins depends on the app

    Three apps in one stack and three different answers to where a config value really comes from, so the same env setup doesn't behave the same way twice.

    I assumed one env setup would work for every app in a local stack, and it doesn’t, because each app decides for itself which value wins.

    The stack renders an env file for each service.

    There are two ways we can hand that to a container, as an env_file in compose, which puts the keys in the process, or as a mounted .env that the app reads itself.

    In my case there are three apps and three different answers.

    App How it loads config What wins
    The smaller app loads .env, skips keys that are already set the process env
    The API reads .env, writes every key over the env the file, except for one pinned key
    The backend service reads the config file a variable points at a file in the checkout, if the variable is empty

    The process wins

    The smaller app’s loader won’t overwrite a key that’s already set, so if a key comes in through env_file and through the mounted file, the mounted file is ignored for that key.

    Both came from the same generated file so nothing broke, but we only need one way in, so a service with a mounted env file doesn’t get an env_file anymore.

    services:
    app:
    volumes:
    - ./app.env:/app/.env:ro

    The file wins, except for one key

    The API is the opposite, it reads .env itself and writes every key over what’s there, so a value you set on the container loses to the file.

    Then I pinned APP_MODE=production in the prod image on purpose, so a dev .env mounted into it can’t switch on error details, and that one key comes from the process now.

    The stack runs the same image, so the value in its env file stopped doing anything, and the API ran as production on a laptop until I fixed it about an hour later.

    So for that key we need to do the opposite of every other key, and set it on the container.

    services:
    api:
    environment:
    APP_MODE: dev

    An empty variable is still a value

    The backend service finds its config through a variable, and when stack run started it from a checkout that variable was empty, so it fell back to a local config file in that checkout.

    That file switched on features the stack didn’t configure, and the service refused to start.

    So a start command now names its config explicitly, even if that config is an empty file shipped with stack.

    If a value you set doesn’t seem to do anything, take a look at how that app loads it first.

    Running docker compose exec api env from ~/.stack/platform shows you what’s in the process, which for the API is only half of it, because the file goes on top.