Environment configuration with React & Nginx

How do you deploy a webpack-generated React+Nginx app to different environments, while allowing it to be configured with different service endpoints? Read on to explore the options, and, in my opinion, the cleanest solution.

I’ve been working with React for a couple of weeks, and something I very quickly wanted to know, was how best to achieve environment-specific configuration with a Webpacked app.

For server-side apps, such as Node.js, Ruby on Rails, or even .Net, reading an environment variable and choosing the right config file, or even just reading everything from environment variables is easy, but when your application is a static, minified package of HTML, CSS, and JavaScript, it’s not quite so simple.

There are a few options you have here, so I’ll give a brief description of each, and I’ll describe the downsides that meant I didn’t choose them. First though, I’ll list the parts of my scenario:

  • React, created using create-react-app, then ‘ejected’
  • Nginx or potentially other static host (eg. CDN)
  • Docker
  • Integrating with a single, remote backend API (for now)

Using React

Webpack on deploy

The documented way seems to be to use process.env.foo in your react app, and let the dotenv plugin for Webpack take care of building the values into the minified code. [1]

On the face of it, this looks reasonable… you configure your Docker container with the environment variables, and just run npm run build in the CMD argument.

For me, this is wholely unacceptable though. The are far too many possibilities for variance, and therefore failure in the process to ‘recompile’ the code for each environment. I need a ‘build once, deploy anywhere' approach.

Another variation on this theme is to build the app with a placeholder in the script, add then use Sed to substitute in the real value on deployment/startup. This again, has too high chance of screwup for my liking.

Host detection

Since all of the environments would have different FQDNs, I could use that as a switch to pick between different set of configuration values.

I discounted this one for two reasons. Firstly, this would mean building in the configuration for all of the environments, exposing the existence and addresses of internal infrastructure and giving away some amount of development process. Secondly, if the browser misinterprets the host name somehow, it could fail inconsistently.

There’s also a slight annoyance with local testing conflicting with a user loading the site having saved it to their machine for looking at later, however, this isn’t a huge concern as other things would break.

Using Nginx configuration

Since the React tooling didn’t appear to have a decent answer, my next thought was to use Nginx — the web server if use to host the built site — to provide the configuration.

I saw a suggestion for doing the placeholder replacement approach using Nginx’s sub_filter module, but I disliked that for the same reason as above.

Separate config file

Of course, the next step was to make the configuration a separate file and somehow (I’ll touch on at the end) bring that file into the app when it’s loaded into the browser.

Something that’s done with server-side code is to store config files outside the public files directory, and then use the environment variable (eg. NODE_ENV) to pick the right config file, and serve that file up as a generic /config.js endpoint. So can we do that with Nginx?

Short answer, no. Long answer, yes, but you’re apparently not supposed to, so they make it hard.

Out-of-the-box, Nginx doesn’t support using environment variables inside most configuration blocks.

You can use the Lua or Perl modules, but that will require installing those modules, making your Docker image bigger, and at that point your site stops being ‘static’ if you’re having to use runtime code.

Generate nginx.conf

Several answers on StackOverflow suggest generating the nginx configuration on deployment/startup. Ranging from Sed, and envsubst, to erb (Embedded Ruby) and other scripting languages.

String replacement methods, like Sed were out, solely for the possible inconsistency when dealing with complex config files. The necessity to run many times for different variables also doesn’t make it easy.

Scripting languages like erb were out as I’d have to install whichever one inside the Docker image in order to use it, and that’s unnecessary bloat (as well adding to the security risk). There’s also the issue with some scripting languages that usage of special characters (eg. $) in the nginx.conf itself might be interpreted, rather than left as literal strings in the config.

Copy the app’s config file into place

Perhaps the simplest approach, but easy to neglect, is to have a simple shell script run prior to Nginx starting. That script reads the environment variable and copies the relevant file from an environment-specific directory to a generic file in the static site directory.

cp /config/$ENVIRONMENT.json /app/config.json

If you’re passing the actual configuration values in, rather than picking a pre-stored file, there’s also the option to generate the config file using a simple echo command

echo ‘{“foo”:”$FOO”}’ > /app/config.json

Copying a pre-made file is currently my preferred approach. There’s always room for error, but this way doesn’t touch any brittle/complex server configuration, and leaves the server as dumb as possible at runtime. You could even run your site from S3/similar, rather than Nginx, if you wanted.

The whole setup ends up looking something like this:



FROM nginx:latest COPY nginx.conf /etc/nginx/nginx.conf COPY build /usr/share/nginx/html/ COPY config/client-side /usr/share/nginx/config/ COPY launch.sh ./ RUN chmod +x launch.sh CMD ./launch.sh ```



cp /usr/share/nginx/config/$ENVIRONMENT.json /usr/share/nginx/html/config.json nginx -g "daemon off;" ```


nginx.conf (snippet)

... http { ... server { ... location / { root /usr/share/nginx/html; index index.html; } } } ```

Including the config in the site

The other side of things that was virtually unexplained, was how to get the config file to stay separate to the main application code, but still be included.

Code splitting

I tried to see if I could get Webpack to leave the config file out of the bundle, and require it at runtime, but I gave up quickly when it wasn’t obvious. I’m sure there’s probably an elegant way to do it here if I could be bothered to find it.

Script tag

Putting the config.js file as a <script> tag in the HTML template worked, but I’d have to rely on a blocking script loading in order to make sure the config was loaded before the application code.

Request it

The easiest way I could think of was to make a module in the application that would make an HTTP request for a relative path /config.json and provide the response to the rest of the application.

The added benefit of requesting it is that I can deal with error handling, as well as a loading spinner, if I wanted one.

The end result

After playing with all of the options, the easiest, as well as more resilient option seems to be to keep separate config files per environment, and request it the first time it’s used in the front-end.

If you don't have a Google+ account you can comment using the normal comment form below

About the author

Portrait of the author

On weekdays I'm a Technical Lead at Comparethemarket.com, having previously been a Solution Architect at Nokia & Nokia Siemens Networks, creating creative software solutions for mobile operators around the world.

In my spare time I'm an avid new technology fan, and constantly strive to find innovative uses for the new gadgets I manage to get my hands on. Most recently I've been investigating Mobile Codes, RFID and Home automation (mainly Z-Wave). With a keen eye for usability I'm attempting to create some cost-effective, DIY technology solutions which would rival even high-end retail products. The software I develop is usually released as Open Source.

I have a Finnish geek partner, so have begun the difficult task of learning Finnish.

Add me to your circles on Google+

The blog
November 2022

Zap the link below with your qrcode enabled mobile to send this page to it
Mobile Code for this page
What's this?