Recreate config file
Environment-specific configuration is essential for deploying web applications across multiple environments like development, staging, and production. However, hard-coding config values makes your application less portable and more brittle. A better approach is to use environment variables that can be easily changed between deploys without modifying the code.
While Node.js provides access to environment variables via process.env, browser-based apps like those made with Create React App (CRA) don‘t have this option. CRA supports build-time environment variables using .env files, but that requires rebuilding for each environment.
Ideally, we‘d like to inject environment variables at runtime when the container starts up. This allows us to build a single Docker image that can be reconfigured on the fly for different environments. By following the 12 Factor App methodology, we can make our application more portable and scalable.
In this guide, I‘ll show you how to implement runtime environment variables in a Create React App project using Docker and Nginx. We‘ll cover the following topics:
- Generating an example CRA project
- Writing a bash script to convert .env to JavaScript
- Modifying the Dockerfile to inject environment variables
- Configuring Nginx to serve the React app
- Running the container with docker-compose
- Deploying to multiple environments
By the end, you‘ll have a React app that can be easily configured at runtime using environment variables. Let‘s get started!
Step 1: Generate a new React project
First, make sure you have Node.js installed. Then open your terminal and run:
npx create-react-app cra-runtime-env-demo cd cra-runtime-env-demo
This generates a default CRA project in the cra-runtime-env-demo directory.
Next, create a file called .env in the project root with some example environment variables:
REACT_APP_API_BASE_URL=https://api.example.com REACT_APP_GA_TRACKING_ID=UA-12345678-1
We prefix the variable names with REACTAPP so CRA will include them in the build.
Step 2: Write a bash script to inject environment variables
To convert our .env file to a JavaScript file that can be imported in the browser, create a new file called env.sh in the project root:
#!/bin/bashrm -rf ./env-config.js touch ./env-config.js
echo "window.env = {" >> ./env-config.js
while read -r line || [[ -n "$line" ]]; do
if printf ‘%s\n‘ "$line" | grep -q -e ‘=‘; then varname=$(printf ‘%s\n‘ "$line" | sed -e ‘s/=.//‘) varvalue=$(printf ‘%s\n‘ "$line" | sed -e ‘s/^[^=]=//‘) fi
value=$(printf ‘%s\n‘ "${!varname}")
[[ -z $value ]] && value=${varvalue}echo " $varname: \"$value\"," >> ./env-config.js done < .env
echo "}" >> ./env-config.js
This script does the following:
- Removes any existing env-config.js file and creates a new one
- Writes JS code to assign a config object to window.env
- Reads each line of the .env file and splits on the = character
- Checks if the variable is set in the environment, otherwise falls back to .env
- Appends each key-value pair to the config JS object
To include the generated config in your app, add this line to the of public/index.html:
<script src="%PUBLIC_URL%/env-config.js"></script>
Now your React code can access the environment variables under window.env. For example:
console.log(window._env_.REACT_APP_API_BASE_URL)
Be sure to add /public/env-config.js to your .gitignore so you don‘t commit it.
Step 3: Configure NGINX
To serve our React app using Nginx, create the following files and directories:
mkdir -p nginx/conf.d touch nginx/conf.d/default.conf
Add the following config to nginx/conf.d/default.conf:
server { listen 80;location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; expires -1; # Set it to different value depending on your standard requirements }
error_page 500 502 503 504 /50x.html;
location = /50x.html { root /usr/share/nginx/html; } }
This tells Nginx to serve files from /usr/share/nginx/html and fallback to index.html for client-side routing. The expires -1 header prevents the browser from caching so it always gets the latest config.
We can also add compression by creating nginx/conf.d/gzip.conf:
gzip on; gzip_http_version 1.0; gzip_comp_level 5; # 1-9 gzip_min_length 256; gzip_proxied any; gzip_vary on;gzip_types application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component;
This enables gzip compression for common web MIME types.
Step 4: Create a multi-stage Dockerfile
With our Nginx config in place, we can now write a Dockerfile that builds the React app and injects the environment config:
# Build app FROM node:alpine as builder WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile COPY . . RUN yarn buildFROM nginx:stable-alpine COPY --from=builder /app/build /usr/share/nginx/html COPY --from=builder /app/env.sh /usr/share/nginx/html COPY --from=builder /app/.env /usr/share/nginx/html COPY nginx/conf.d /etc/nginx/conf.d RUN apk add --no-cache bash RUN chmod +x /usr/share/nginx/html/env.sh CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""]
This multi-stage Dockerfile uses node:alpine to build the React app, then copies the static files to an nginx:stable-alpine image. It also copies over our env.sh script and .env file, and sets up the Nginx config.
The CMD directive runs our bash script to generate the config and starts the Nginx server.
Step 5: Use docker-compose to build and run
We‘re now ready to build our Docker image and run the container. While you could use docker build and docker run directly, it‘s often more convenient to use docker-compose.
Create a docker-compose.yml file in the project root:
version: ‘3‘ services: web: build: context: . dockerfile: Dockerfile image: cra-runtime-env env_file: .env environment: - PORT=8080 ports: - 80:80
This defines a web service that builds our Dockerfile and starts the container with the environment variables from the .env file. It also exposes port 80.
To build the image and start the container, run:
docker-compose up --build
Once it‘s done, you should be able to access the app at http://localhost.
Step 6: Deploy to multiple environments
With our Docker setup complete, we can now easily deploy to multiple environments just by changing the environment variables.
For example, to deploy to a staging environment, you could create a docker-compose.staging.yml file:
version: ‘3‘ services: web: image: cra-runtime-env env_file: .env.staging environment: - PORT=8080 ports: - 80:80
This uses the same image but mounts a different .env.staging file with staging-specific variables.
Then to deploy:
docker-compose -f docker-compose.staging.yml up
You can create compose files for as many environments as you need.
Conclusion
In this guide, we covered how to implement runtime environment variables in a Create React App project using Docker and Nginx. The key steps were:
- Generating the React app
- Writing a bash script to inject variables
- Configuring Nginx to serve the app
- Building a Docker image with multi-stage builds
- Running the container with docker-compose
- Extending to multiple environments
While this approach takes some initial setup, it makes your React app much more flexible and portable. By injecting environment-specific config at runtime, you can build once and deploy anywhere without changing your code.
I hope you found this guide useful! Feel free to adapt the code for your own projects. The full example code is available on GitHub at:
https://github.com/yourusername/cra-runtime-env-demo
If you have any questions or suggestions, let me know in the comments. Happy coding!