Config Files vs Environment Variables — When Your App Needs a Real File

·6 min read·Updated
configurationdockerenv-varsself-hostedhow-to

Use environment variables for values your own code reads, and config files when the software you're running only knows how to read a file. That's the whole rule, and it matters because a lot of the best self-hostable software predates the era of configuring everything through the environment — Mosquitto, nginx, Redis, and most Java tooling want a file at a path, not a variable. On JustRunMy.App both are first-class: env vars are set per app, and config files are uploaded and mounted into the container read-only at a path you choose. Both trigger a restart when you change them, and both have validation rules that will reject your input up front rather than failing at container start.

When you need an actual file

Env vars win on convenience: nothing to upload, easy to change, and they compose well with secrets. Reach for a config file when:

  • The software has no env-var equivalent. mosquitto.conf, redis.conf, nginx.conf, prometheus.yml — these are the app's configuration language, and there's no MOSQUITTO_LISTENER to set instead.
  • The content is structured. A YAML rules file or a JSON service-account key is awkward to squeeze through a single variable, and much harder to read back when something's wrong.
  • A library expects a path. Plenty of SDKs want GOOGLE_APPLICATION_CREDENTIALS=/etc/gcp/key.json — a path, which means the file has to exist somewhere.

That last one is the common hybrid: the file is mounted as a config file, and an env var points at it.

How config-file mounts work

You upload the content and give it a container path — /mosquitto/config/mosquitto.conf, /etc/nginx/conf.d/default.conf, whatever the software expects. The platform mounts it into the container at that path.

Three properties worth knowing:

They're read-only. The mount is :ro, so your application cannot write to it. That's usually right — configuration shouldn't be rewritten by the thing it configures — but it does mean software that expects to update its own config file in place will fail. If a tool rewrites its config as part of normal operation, give it a volume instead and seed the file on first run.

Saving one restarts the app. The mount is applied when the container is created, so a config change and a restart are the same event. Expect the downtime; don't be surprised by it.

The path must be absolute and canonical. /etc/app/config.yml, not etc/app/config.yml. Trailing slashes and doubled separators are normalized, so /data, /data/ and //data are the same target — you can't add the same path twice by writing it differently.

See Config Files for the panel steps.

Paths you can't use

Some mount targets are rejected on purpose:

  • /proc, /sys, /dev — kernel pseudo-filesystems. Mounting over them breaks the container in ways that are painful to diagnose.
  • /var/jrnm and the platform's tooling paths — the container's entrypoint and helper binaries live there. Shadowing them stops the container starting at all.

You'll get a clear error while editing rather than a container that fails to boot, which is the point of validating up front.

What env vars can actually hold

More than people assume. Two limits worth knowing:

  • Values can be up to 128 KB, names up to 255 characters.
  • Newlines and tabs are allowed in values. This is deliberate — a PEM certificate or private key is a legitimate multi-line value, and rejecting line breaks would make those impossible.

So yes, you can put a certificate in an environment variable. Whether you should depends on the consumer: if your code reads the PEM from the environment, that's fine and avoids a file entirely. If the library wants a path, use a config file instead of writing the variable to disk at startup.

Other control characters are rejected, and so is = in a name — Docker splits each entry on the first =, so a name containing one would silently land the value under a different variable than you asked for.

DF_ is reserved

Environment variable names beginning with DF_ are rejected. The platform injects its own under that prefix — DF_URLS with your app's public HTTPS URLs, DF_VOLUMES with the mounted volume paths, plus internal API values.

Two consequences:

  1. Don't prefix your own variables with DF_. Pick anything else; the rejection is immediate, not silent.
  2. DF_URLS is genuinely useful. Reading your own public URL from the environment beats hard-coding it — that's how a staging app ends up handing out production links.

Neither one is a secret store

Worth being plain about this. Env vars and config files are both visible to anyone with access to the app in the panel, and both are readable from inside the container by anything running there. They keep credentials out of your repository, which is the main thing, but they are not a vault.

Practical posture:

  • Keep tokens and keys out of the image and out of git; set them as env vars or config files at deploy time.
  • Rotate anything that's been in a screenshot, a public repo, or a shared terminal.
  • Prefer the platform prompting you for a value at deploy time over pasting it into a file you might commit later.

A worked example

Eclipse Mosquitto needs a config file before it will accept a non-local connection, and the config references a data directory. That's all three mechanisms at once:

  • Config file at /mosquitto/config/mosquitto.conf — the listener and persistence settings.
  • Volume at /mosquitto/data — where the persisted message store actually lives, because a config file mount is read-only and a container's writable layer doesn't survive.
  • Port binding for the broker's TCP listener.

The full walkthrough is in deploying an MQTT broker — it's a good template for any config-file-driven service.

FAQ

Should I use environment variables or a config file?

Env vars for values your own code reads. Config files when the software only knows how to read a file — Mosquitto, nginx, Redis, Prometheus and similar.

Can my app write to its config file?

No. Config files are mounted read-only. If the software rewrites its own configuration, use a volume and seed the file on first run instead.

Do config file changes take effect immediately?

They apply on restart, and saving a change triggers one — the mount is established when the container is created.

Can I store a certificate in an environment variable?

Yes. Values may be up to 128 KB and newlines are allowed, so PEM content is fine. Use a config file instead if your library wants a filesystem path.

Why was my variable name rejected?

Most likely it starts with DF_, which the platform reserves for its own injected variables, or it contains = or a control character.

Which paths can't I mount to?

/proc, /sys, /dev, and the platform's own tooling paths under /var/jrnm. Paths must also be absolute.

Wrapping up

The choice isn't really about preference — it's about what the software on the other end can read. Modern code you wrote yourself takes env vars happily; a decade-old daemon takes a file and nothing else. Knowing that config mounts are read-only, that changes restart the app, and that DF_ belongs to the platform covers most of what goes wrong before it does.