Ask
28

Environment variables are fine locally and undefined in the build, where are they supposed to live?

The app works perfectly in development. I have a local file with the API URL and a couple of keys, and everything reads them fine.

The build produced by the build service has none of them. The app launches and immediately fails because the API URL is undefined.

I understand roughly that the build happens on someone else's machine and my local file is not there. What I cannot work out is where the values are supposed to go instead, and whether they are baked in at build time or read when the app runs.

That distinction seems to matter a lot and every guide I read assumes I already know it.

8 answers Share
Report

Answering anonymously, a moderator will review it first.

  • @build_time_vs_run · 3w ago · 2 replies

    That distinction is the whole thing, and everything else follows once you have it.

    A mobile app has no environment at runtime. There is no shell, no process environment, nothing to read from. So anything your JavaScript reads as a configuration value has to be baked into the bundle at build time. It becomes a literal string in the shipped code.

    Which means your local file works in development for a reason that does not generalise: the development server is running on your machine, in your shell, and substitutes the values as it bundles. On a build machine, that file does not exist, so the substitution produces undefined: which is exactly what you shipped.

    So the answer to where they go: into the build configuration, per build profile, so the build service has them when it bundles.

    The consequence people miss, and it matters more than the mechanics: a value baked into the bundle is readable by anyone with the app. Not obscured, not protected - present as a string in a file anyone can extract. So this mechanism is for configuration, never for secrets.

    If something must stay secret, it belongs on a server you control, and the app calls that server. There is no arrangement in which a key in a mobile binary is private.

    30
    Share
    Reply

    Answering anonymously, a moderator will review it first.

    Report
    • @leaked_a_key · 3w ago

      No environment at runtime is the sentence to remember. Values are baked in at build time or they are not there at all.

      10
      Share
      Reply

      Answering anonymously, a moderator will review it first.

      Report
  • @eas_secrets_now · 2w ago

    Anything genuinely secret goes behind your own endpoint. The app calls you, you call them.

    7
    Share
    Reply

    Answering anonymously, a moderator will review it first.

    Report
  • @profiles_not_files · 3w ago · 2 replies

    Practically, put the values in build profiles rather than in a file, because a file that works is a file that is either committed or absent, and both are bad.

    The shape that works:

    A profile per target - development, preview, production: each with its own values. Then building for production picks up production configuration without you remembering anything.

    Values that differ per environment go in the profile. API URLs, feature flags, the analytics key for that environment.

    Values that are genuinely sensitive go in the build service's secret storage, which keeps them out of your repository. Note this protects them from your git history, not from the shipped app - see the previous answer.

    Keep the local development file for convenience, and make sure it is git-ignored and that nothing depends on it existing.

    Two things to check once you have done it:

    Confirm the value made it into the bundle rather than assuming. Log it once at startup in a preview build. Undefined at runtime and undefined at build time look identical from the app's side and the fix is different.

    Watch out for names that are only substituted with a specific prefix. Bundlers commonly only inline variables whose names match a convention, and a value with the wrong prefix silently becomes undefined without any warning.

    1
    Share
    Reply

    Answering anonymously, a moderator will review it first.

    Report
    • @eas_secrets_now · 3w ago

      On where they live: build profiles, and the reason a file feels fine locally is that development reads it from your machine, which the build machine has never seen.

      Once you have that distinction the whole class of problem disappears, because you stop expecting a file that is not in the repository to arrive somewhere it was never sent.

      18
      Share
      Reply

      Answering anonymously, a moderator will review it first.

      Report
  • @secrets_are_not · 3w ago · 3 replies

    Adding the practical audit, because almost everyone reading this has already shipped something they should not have.

    Take a release build and look inside it. The bundle is a file; you can extract it and search it. Search for anything that looks like a key. It takes ten minutes and the results are usually educational.

    What commonly turns up and is fine: API base URLs, public identifiers meant to be public, analytics keys designed to be client-side, publishable payment keys.

    What turns up and is not fine: anything with the word secret in its name, database credentials, admin tokens, a third party API key that allows writes.

    If you find one of the second kind, the fix is not to hide it better. It is to rotate it and move the operation behind an endpoint you control, with the app authenticating as a user rather than holding the key.

    One related thing worth knowing while you are in here: over-the-air updates carry the bundle, not the native configuration. So a value baked at build time stays whatever it was when the binary was built, even after an update. People change a profile, push an update, and are confused that the old URL is still in use - the update replaced the JavaScript, not the build-time substitution that happened before it.

    1
    Share
    Reply

    Answering anonymously, a moderator will review it first.

    Report
    • @leaked_a_key · 3w ago · 2 replies

      Shipped an API key inside a bundle and found out when the provider emailed me about usage from an address I did not recognise. The audit in the third comment is not theoretical.

      The thing worth internalising: anything the app can read, someone with the app can read. There is no client-side hiding place, only obscurity, and obscurity is a delay rather than a defence.

      23
      Share
      Reply

      Answering anonymously, a moderator will review it first.

      Report
      • @secrets_are_not · 3w ago

        Almost everyone has shipped one. The useful question is whether you know which ones and can rotate them.

        14
        Share
        Reply

        Answering anonymously, a moderator will review it first.

        Report