Dockerizing Your React App
The team at Facebook has made it very easy for someone to bootstrap a React application with their create-react-app CLI tool. You can literally have a fully-configured React application running and ready to add components within a minute or two. Things get a little more complicated when you want to deploy your app in a Docker container and adhere to 12-factor-app principles of using environment (ENV) variables to configure your app. Worry no more!
TL;DR
For the impatient folks who just need a solution, visit this Github repo I put together to demonstrate exactly how to add Docker and ENV variables to your React app.
Why is this important?
More companies (and individuals) are deploying their software within containers. Best practices adhere to the 12-factor-app methodology and in particular, avoiding hard-coding application configuration in your app itself. Since you can pass environment variables into your container at runtime, you can reuse the same image and change configuration of your application by passing different arguments as you spin up your instance.
Background
React apps bootstrapped with create-react-app include a development server, and preconfigured webpack settings to compile and produce a production deployment. When you deploy your app within Docker, you want the production build version. Unfortunately if you execute the npm run build --production command in your Docker image, then the environment variables you pass to the runtime container are not accessible.
Key challenges and solutions
- React apps ignore ENV vars unless prefixed with REACT_APP_<var name>. I believe the only exception may be the NODE_ENV var, but others must be prefixed to be used in your app.
- If you build your production app within your image, it will not recognize ENV vars at runtime (unless you pass them as build args during image creation). Because of this, your image should install all necessary dependencies and copy your source files, but your build and server start should be run in a script executed during the CMD step.
Example use case: change database host per environment
A popular use of ENV variables for application configuration is dynamically declaring a development database host, a test database host, and a production database host. You can simply change the environment variable for each environment and they all run the same app, but it connects to different databases. See: https://12factor.net/config
Using ENV variables within your app
const dbHost = process.env.REACT_APP_DB_HOST;
const dbConn = client.connect(dbHost); // illustrative purposes only
If you have several variables, I recommend declaring them all in a single file that you can import into other files and reference the variables. You can see an example of this in the Github repo link above.
Configuring your Docker image (Dockerfile) to run a script
FROM node:9.4
# Create app directory
WORKDIR /usr/src/app
# Expose port for service
EXPOSE 5000
# Install and configure `serve`.
RUN npm install -g serve
# Copy source code to image
COPY . .
# Install dependencies
RUN npm install
# Build app and start server from script
CMD ["/usr/src/app/run"]
Adding your run script (remember to make file executable permission)
#!/usr/bin/env bash
echo "===> Building ..."
npm run build --production
echo "===> Running ... "
exec serve -s build
The Github repo example adds more options, but the basics include building the app, and then starting the server to serve up the compiled version in the build directory.
Building and running your Docker image and changing configuration
# Assume in your app you connect to a database
# DEV: set REACT_APP_DB_HOST=localhost
# TEST: set REACT_APP_DB_HOST=testdb.myco.com
# PROD: set REACT_APP_DB_HOST=proddb.myco.com
# build Docker image (one time)
docker build -t myimage .
# run local development instance
docker run --name myapp -p 8080:5000 \
-e REACT_APP_DB_HOST=localhost --rm myimage
# run testing instance
docker run --name myapp-test -p 8081:5000 \
-e REACT_APP_DB_HOST=testdb.myco.com --rm myimage
# run production instance
docker run --name myapp-prod -p 8082:5000 \
-e REACT_APP_DB_HOST=proddb.myco.com --rm myimage
Finally, as this example illustrates, you can build a single app and image once and reuse it. Each instance, depending on what environment variables you declare, determines what database the app connects to. If you visited port 8080, you would run the app on local database. Ports 8081 and 8082 respectively would all run against their respective databases. You can apply this same technique to configuring anything you wish in your app to behave differently each instance.
Conclusion
For those familiar with NodeJS/Express apps, the process.env syntax should be familiar. The tricks with passing environment variables for use in the app requires building the app at runtime via a script, and remembering to name your ENV variables with REACT_APP_ prefix. I hope this helps you "Dockerize" your React apps and adhere to 12-factor-app best practices for configuration.
Hi Mike, great write-up! I've been looking into a solution for runtime vs compile time env args using Docker and React. I stumbled upon your article after seeing your post referenced in a few places (links at bottom). While your post solves the problem, it also couples the build vs run stages of the image, since the webpack build happens on the host machine running the Docker image. This can be troublesome since a webpack build is often not a trivial process (depending on size of your JS app), and it's also not a pure "run" phase. Is this an issue that you've considered while designing your solution, and/or is there something that I'm overlooking? Cheers, Michael https://github.com/facebook/create-react-app/issues/982#issuecomment-393601963 https://stackoverflow.com/questions/49975735/rendering-an-environment-variable-to-the-browser-in-a-react-js-redux-production https://github.com/facebook/create-react-app/issues/578
Data Engineer | Making Robust Data System
7ywhy you put database connection on a react app? shouldn't it be talked to REST/GraphQL ? opening db connection to the internet is dangerous.
I came upon this article while searching for "12-factor-app methodology", but it answered a running problem that I've had: how to pass custom variables from docker to a CRA instance. I'm sure that I was using the wrong search terms before, but using the magical REACT_APP_ prefix should be more easy to find, I think. Thanks.