← Blog

When a secret has to be a file, not an environment variable

Railyard

September 18, 2026

Most of what an app needs is a plain KEY=VALUE pair, and most PaaS platforms are built entirely around that assumption. Then a Google Cloud service account JSON shows up — a few kilobytes of structured credential data that Google’s own SDKs expect to find at a file path, not inside an environment variable — and the usual workaround is base64-encoding the whole thing into a var and decoding it in an entrypoint script. It works, but it’s a workaround, not a feature.

The actual mechanism

Variables → File secrets takes a name and the content directly — up to 64 KB, enough for a service account JSON, a client certificate, or similar structured credentials. It’s stored encrypted, same as any other secret, and mounted read-only inside the container at /etc/railyard/secrets/<name> — no base64 round-trip, no entrypoint script decoding a variable before the app can start.

The part that removes the usual second step: its path is also exposed as its own environment variable, RAILYARD_SECRET_FILE_<NAME>. A Google client library expecting GOOGLE_APPLICATION_CREDENTIALS to point at a file just gets:

GOOGLE_APPLICATION_CREDENTIALS=${RAILYARD_SECRET_FILE_GCP_KEY_JSON}

No extra step resolves that reference — it’s the same variable-reference mechanism as everything else, pointing at a path instead of a value. Removing a file secret takes effect on the next deploy, same as any other variable change.

Where this sits next to everything else that’s already automatic

Provisioned services already inject their own variables without being asked — DATABASE_URL, the individual DATABASE_HOST/PORT/USER/PASSWORD/NAME fields, REDIS_URL, storage keys, RAILYARD_URL and friends. An app’s own documented variables (the specific names it lists needing, like PGHOST or BIND_ADDRESS) get filled from those services automatically where the names overlap, and secrets the app itself owns — SECRET_KEY_BASE, APP_KEY, JWT_SECRET — are generated once rather than left blank. What none of that mechanism does, deliberately, is invent a third-party credential on your behalf — a GCP service account, a Stripe key, anything that actually belongs to an outside account. Those are always yours to provide, which is exactly the category file secrets exist for.

Referencing a service instead of hardcoding a host

The same ${{...}} reference syntax used for file secret paths works across services generally: ${{api.RAILYARD_PRIVATE_URL}} resolves to that service’s private address within the same environment, and ${config.KEY} pulls from team-wide shared config available to any app — the same pattern, three different sources, so an app never has a hardcoded host or a copy-pasted secret drifting out of sync with where the actual value lives.

Full reference: Variables and secrets.

Add a file secret under Variables.

Stored encrypted, mounted read-only, its path exposed as an env var automatically.

Free plan, no card, and 14 days of Pro for every new team. One flat price per team.