Runtime Environment Variables for Dockerized React App

Posted Thursday, January 14, 2021.

I had the requirement for runtime variables in a dockerized react application. When you have a CRA and use the environment files .env / .env.development / .env.production, the variables get baked in at build time. This does not allow you to dynamically inject environment variables when running your docker containers. I found a couple solutions while searching for a remedy and this is my combined solution to the problem.

Project Setup

You will want your project folder to look like the following:

.
├── src
│ └── ...
├── ...
├── .env
├── .env.development
├── .env.production
├── docker-entrypoint.sh
└── Dockerfile

The .env and .env.development files will be treated normally, but we will do something special with the .env.production file. Remember, when you do a production build, the values in the .env.production file (and any values in .env that are not overriden in .env.production) will be baked into the build.

Suppose you had 2 variables that you wanted to inject at runtime, REACT_APP_API and REACT_APP_TOKEN (remember, they need to be prefixed with REACT_APP for them to be picked up by react). Your environment files might look like:

// .env / .env.development
REACT_APP_API=http://localhost:8080/api
REACT_APP_TOKEN=token123test
// .env.production
REACT_APP_API=https://mycoolapi.com/api
REACT_APP_TOKEN=token123real

In order to have these variables be replaced at runtime in a production built docker container, we need to make modifications to the .env.production file. Make every key you want to be replaced to also be that key's value. In our example that would change our production environment file to:

REACT_APP_API=REACT_APP_API
REACT_APP_TOKEN=REACT_APP_TOKEN

Because we didn't change .env or .env.development, our development environment will not be changed. But now we need some way to replace those variables at runtime. We will do this using a custom docker-entrypoint script.

Entrypoint Script

Edit your docker-entrypoint.sh script to look like the following:

#!/bin/bash

set -e

echo "Replacing environment variables"

replace() {
find . -name '*.js' | xargs sed -i "s|$1|$2|g"
find . -name '*.html' | xargs sed -i "s|$1|$2|g"
}

# replace all the environments
replace REACT_APP_API ${REACT_APP_API}
replace REACT_APP_TOKEN ${REACT_APP_TOKEN}

echo "launch command"
exec "$@"

For each variable that you want replaced at runtime from the .env.production file, add a line calling the replace function. When you build your app, it will replace all the variables in .env.production... to their variable names. This might seem odd, but it is done so that we can use the sed command to replace that key again at runtime, with the injected value from the environment of the docker container. Afterwards, it launches the command with exec "$@".

Dockerfile Changes

In order to have this script run on entry of the container, you will need to adjust your dockerfile to copy the script and make it executable. See below:

FROM node:14-slim

RUN yarn global add serve

WORKDIR /app
COPY . ./
RUN yarn && yarn build

# copy entrypoint script & make executable
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

EXPOSE 8080

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["serve", "-l", "8080", "-s", "."]

Now you can run your container with environment variables:

docker run -e REACT_APP_API=https://mycoolapi.com/api -e REACT_APP_TOKEN=token123real my_cool_container

This could be used so that the same docker image could be used in multiple situations with different APIs (or whatever other reason you have for needing runtime environment variables).

In Closing

This is sort of a hack, but has worked well for me. A downside of this approach is if you add/delete an environment variable, you need to remember to update those changes a few times in a few different places. If there is a better way to go about this, please let me know!


Tagged With: