Running React application in Docker. With runtime configuration.
Create-react-app is great. So great that it has become a de-facto standard for creating new React applications. Docker is great as well. However it seems there’s no canonical way of creating a Docker image to run a create-react-app. Especially when you need to configure your React app on runtime. Let’s see how to do that.
The problem
The goals of the project are two fold:
- We want to compile a Create-react-app into Docker image and deploy on our infrastructure. The image shall contain compiled JavaScript, CSS files and other assets and be served by a web server.
- We want to configure the application on runtime. Imagine our application communicates with a REST API on backend. URL of the backend changes depending on what environment we are running in. Application running in production will use production API, application in staging will use staging API and so on. We want to compile the image only once and be able to deploy the same image in both environments.
To tackle the building problem we could use a two-step build process in our Dockerfile. In the first step we would use a build environment where we would copy our application files and build them. In the second step we would copy the compiled files to a new environment, install web server and have it serve the files.
Unfortunately this approach doesn’t provide us with much in terms of possibilities for runtime configuration. React application gets compiled into one static JavaScript file which is then sent from web server and executed in web browser on client’s side. There is just no easy way for us to insert runtime configuration once the file is compiled.
That’s why we are going to run the build process again when starting the container. We are basically going to copy our application files to the build environment and write a script which will first rebuild the application and then start the webserver. We will ensure the separation of source files and application files by copying them over to webserver’s document root.
The Solution
First the Dockerfile:
# Use build environment FROM node:jessie as build-env WORKDIR /app # Install git and openssh in build environment, required by some packages in npm install RUN apt-get update && \ apt-get install -y git openssh-client # Install npm packages. Done in a separate step to have node_modules cached by Docker COPY package.json ./ COPY package-lock.json ./ RUN npm install # Copy source code COPY . ./ # Build the application RUN npm run build # Build runtime image FROM node:jessie # Copy all files, including node_modules WORKDIR /app COPY --from=build-env /app /app # Install npm in runtime environment RUN apt-get update && \ apt-get install -y nginx # Set run command to execute script # The script will run npm build again, but using environment variables provided on runtime CMD ["/bin/sh", "/app/docker_run.sh"]
Then we need the docker_run.sh script:
echo "Building package" npm run build --production echo "Copying files" cp -R /app/build/* /var/www/html echo "Starting nginx" /usr/sbin/nginx -g "daemon off;"
Now we can start using environment variables in our application. We can use process.env.REACT_APP_API_URL anywhere in our React code and pass environment variable REACT_APP_API_URL when starting the Docker container.