Environment variables set by flyctl secrets

It might be a problem of how a setup the RUN commands in the Dockerfile. I used multiple RUN commands which probably is wrong. I am not quite sure how to pass multiple mount commands within the RUN command.

The following did not seem to work either as I got the build error “can’t open ‘/run/secrets/AUTH_DOMAIN’: No such file or directory”. This error occurred for each cat.

RUN --mount=type=secret,id=STORAGE_BUCKET \
    STORAGE_BUCKET="$(cat /run/secrets/STORAGE_BUCKET)" \
    --mount=type=secret,id=AUTH_DOMAIN \
    AUTH_DOMAIN="$(cat /run/secrets/AUTH_DOMAIN)" \
    --mount=type=secret,id=PROJECT_ID \
    PROJECT_ID="$(cat /run/secrets/PROJECT_ID)" \
    --mount=type=secret,id=STORAGE_BUCKET \
    STORAGE_BUCKET="$(cat /run/secrets/STORAGE_BUCKET)" \
    --mount=type=secret,id=MESSAGING_SENDER_ID \
    MESSAGING_SENDER_ID="$(cat /run/secrets/MESSAGING_SENDER_ID)" \
    --mount=type=secret,id=APP_ID \
    APP_ID="$(cat /run/secrets/APP_ID)" \
    --mount=type=secret,id=ROOT_DIRECTORY \
    ROOT_DIRECTORY="$(cat /run/secrets/ROOT_DIRECTORY)" \
    npm run build

You need a single RUN command, and all the --mount options need to be at the start:

RUN --mount=type=secret,id=STORAGE_BUCKET \
    --mount=type=secret,id=AUTH_DOMAIN \
    --mount=type=secret,id=... \
    STORAGE_BUCKET="$(cat /run/secrets/STORAGE_BUCKET)" \
    AUTH_DOMAIN="$(cat /run/secrets/AUTH_DOMAIN)" \
    ...="$(cat /run/secrets/...)" \
    npm run build

Just out of interest, why are you going with build secrets rather than a .env file?

You mean like including a .env file in the root directory? If so, I was told that the Firebase connection parameters should be kept secret and not added in the source code etc. As I have understood, including those values as a .env file in the root directory would mean that it can get traced in the image. Also, using ARG ENV in the Dockerfile results in the same problem.

Tried your RUN command but still the same error.

You should have that reviewed.

React embeds the environment variables in the generated HTML/JS files, so the values will be included in the image; and further, anyone that can view the deployed app will be able to see them (docs). All the code runs on the client side, so there isn’t any other way really.

Using build secrets won’t help. You can easily verify this: set one of your build secrets to a searchable value (e.g. test16682), and search for that value by running e.g. grep -r test16682 . after the command npm run build. This will show you that there are output files that contain this value. [Edit: Consider adding the -l option to list the matching files without displaying the matched lines, since the output files have very long lines.] You could then deploy the app and view the source of those files using a web browser, confirming that clients can see the values.

With Firebase, it is okay to expose the API key (docs), so I think this is fine. However, it is dangerous to think that build secrets will protect the values.

Edit: To clarify, the advice to keep secrets out of the image is correct in general, just not in this particular case of React + Firebase. And build secrets are better than a .env file or ARG for keeping the secrets out of the image, however even with build secrets it is difficult to make sure the secrets aren’t embedded in the image.

Edit:

I noticed that a bit late. Not sure why it would fail; for future reference, could debug by changing npm run build to ls /run/secrets to see which secrets are being mounted.

Thank you Tom for responding to all of my questions and for providing solutions to my nonsense. Also thank you for clarifying secrets and how it all works, I have learnt a lot :slight_smile:

You’re welcome :‍)

I am currently looking for the Firebase parameters in the browser of a deployed app, but can’t seem to find them. Is there any particular place they are located?

Using the command grep resulted in either Permission denied or Input/output error.

React inlines the environment variables, e.g. if your code is console.log(process.env.REACT_APP_FIREBASE_API_KEY); then the generated code in the bundle will be console.log("test16682");.

Note that only env vars that start with “REACT_APP_” are supported, to avoid inadvertently exposing other environment variables.

For grep, make sure you are running it on the app directory rather than on / (entire filesystem). Your Dockerfile’s first stage uses WORKDIR /app, so commands in the first stage should be fine; but if you run grep in the second stage or from SSH you’ll need to specify the appropriate path, e.g. grep -r PATTERN /usr/share/nginx/html (or for convenience, first run cd /usr/share/nginx/html to set the current working directory (.) to the appropriate path, then run grep -r PATTERN .). Consider adding -l (lowercase L; e.g. grep -r -l PATTERN PATH) to list the matching files without displaying the matched lines, since the generated bundle has very long lines.

If I understood correctly, once in the /usr/share/nginx/html directory I ran the grep -r REACT_APP_FIREBASE_API_KEY and got a bunch of text out. Also tried with grep -rl and got the corresponding files. Also tried grep for the secret which did not give any result.

Also, not sure what you meant by console logging the environment variables, I am not able to console log any of the environment variables. It results in the error caught ReferenceError: REACT_APP_FIREBASE_API_KEY is not defined
at :1:13
(

That is by trying console.log(process.envREACT_APP_FIREBASE_API_KEY) and by console logging “process” the error of process is not defined. You said the environment variables should be available to inspect in the broweser? That is only if the environment variables gets console logged in the code?

Edit:
Think I found it in the main.chunk.js file. The grep made me find the correct file and I just searched for REACT_APP and the value was shown.

Due to React’s inlining, console.log(process.env.REACT_APP_FIREBASE_API_KEY); should work while console.log(process); should give you ReferenceError: process is not defined. (You are using Create React App, right?)

But note that this will only work from your src/*.js files (which undergo the inlining), not interactively in the web browser console.

If the code in the generated bundle is console.log(process.env.REACT_APP_FIREBASE_API_KEY); instead of console.log("test16682");, it suggests that the inlining failed (e.g. because env var was not set when npm run build ran, or because you forgot the process.env. part, or because the env var’s name didn’t start with “REACT_APP_”).

If you still have trouble, I suggest you take baby steps starting from a version with an .env file (since you managed to get that working before):

  • Make sure that with an .env file you can use

    console.log(process.env.REACT_APP_FIREBASE_API_KEY);
    

    from src/*.js.
    (Maybe also look at generated bundle to confirm inlining happened as expected.)

  • Switch to an env var by removing the .env file and setting the build command in the Dockerfile to

    RUN REACT_APP_FIREBASE_API_KEY=test16682 npm build
    

    Make sure console.log() still works.

  • Switch to a build secret by setting the build command to

    RUN --mount=type=secret,id=REACT_APP_FIREBASE_API_KEY \
        REACT_APP_FIREBASE_API_KEY="$(cat /run/secrets/REACT_APP_FIREBASE_API_KEY)" \
        npm run build
    

    and deploy using fly deploy --build-secret REACT_APP_FIREBASE_API_KEY=test16682.


When I said you could view the source of those files using a web browser, I meant that if you know which generated file contains the secret (e.g. build/static/js/main.XXX.js), you ought to be able to view that file from a web browser (e.g. open the app in the web browser, use “View Source”, then edit the URL to point to the generated file, maybe view-source:https://APP_NAME.fly.dev/static/js/main.XXX.js). Then use “Find” to look for the secret. If you find it, you have proof that it’s public. [Edit: The value will be visible as long as the variable is used somewhere in your code, doesn’t have to be via console.log().]


General tip: You’ll usually get better help if you can provide a “minimal reproducible example”, e.g.

Run npx create-react-app my-app, append console.log(process.env.REACT_APP_FIREBASE_API_KEY); to my-app/src/index.js, create Dockerfile with content <XXX> [text please – not image], run fly ..., open the app in a web browser. Expect secret to be displayed in browser’s web console, but instead get <full error message>.

(Obviously it’s not always possible to create such an example, but when it is it can make debugging much easier.)

Used build secret:

RUN --mount=type=secret,id=REACT_APP_FIREBASE_API_KEY \
    REACT_APP_FIREBASE_API_KEY="$(cat /run/secrets/REACT_APP_FIREBASE_API_KEY)" \
    npm run build

and deployed using:
fly deploy --build-secret REACT_APP_FIREBASE_API_KEY=test16682

Found it in the browser at view-source:https://APP_NAME.fly.dev/static/js/main.XXX.js.

image

And in the sources:

Still not sure what you mean by console.log and where to execute console.log(process.env.REACT_APP_FIREBASE_API_KEY); There is no console.log(process…) in the source code.

From the console in the browser:
image

You don’t need to spend more time helping me with this as I have found evidence on the secret already, which is great! :slight_smile: I apologize again for my lack of expertise in this.

I’m glad you found it :‍)

At first I just used it as an example to explain how the inlining works, then later I meant for you to insert that line into one of the src/*.js files for debugging because it sounded like you were having trouble getting the values of the variables.

It might still be worth trying that out just to make sure that you are able to get those values, because the behaviour of inlining is so weird (namely, the code should work if you insert it in src/*.js, even though the exact same code doesn’t work when you type it interactively in the web browser console). Also, watch out if you use inline <script> elements – I believe their code doesn’t undergo inlinining, so process.env wouldn’t work inside them.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.