Containerising your projects with Docker simplifies the development experience and facilitates straightforward deployment to cloud environments. Let’s look at how we can package a React site as a Docker container.
This article focuses on projects that have been bootstrapped using
create-react-app (CRA). If you’ve ejected your CRA configuration, or are using a custom build process, you’ll need to adjust the
npm run build command accordingly.
Docker images are created via a
Dockerfile. This defines a base image to use, such as the Apache web server. You then list a series of commands which add packages, apply config changes and copy in files needed by your application.
Defining Our Requirements
CRA includes a built-in live build and reload system, which you access via
npm run start. This enables you to quickly iterate on your site during development.
When moving to production, you need to compile your static resources using
build directory. These files are what you upload to your web server.
A basic approach to Dockerising could be to
npm run build locally. You’d then copy the contents of the
build directory into your Docker image – using a web server base image – and call it a day.
This approach doesn’t scale well, particularly when building your Docker image within a CI enviroment. Your app’s build process isn’t completely encapsulated within the container build, as it’s dependent on the external
npm run build command. We’ll now proceed with a more complete example where the entire routine runs within Docker.
A Dockerfile For CRA
FROM node:latest AS build WORKDIR /build COPY package.json package.json COPY package-lock.json package-lock.json RUN npm ci COPY public/ public COPY src/ src RUN npm run build FROM httpd:alpine WORKDIR /var/www/html COPY --from=build /build/build/ .
Dockerfile incorporates everything needed to fully containerise the project. It uses Docker’s multi-stage builds to first run the React build and then copy the output into an
alpine Apache server container. This ensures the final image is as small as possible.
The first section of the file defines the build stage. It uses the official Node.js base image. The
package-lock.json files are copied in.
npm ci is then used to install the project’s npm packages.
ci is used instead of
install because it forces an exact match with the contents of
Once the dependencies are installed, the
src directories are copied into the container. The folders are copied after the
npm ci command because they’re likely to change much more frequently than the dependencies. This ensures the build can take full advantage of Docker’s layer caching – the potentially expensive
npm ci command won’t be run unless the
package-lock.json files change.
The last step in this build stage is to
npm run build. CRA will compile our React app and place its output into the
The second stage in the
Dockerfile is much simpler. The
Using The Docker Image
docker build command to build your image:
docker build -t my-react-app:latest .
This builds the image and tags it as
my-react-app:latest. It uses the
Dockerfile found in your working directory (specified as
The build may take a few minutes to complete. Subsequent builds will be faster, as layers like the
npm ci command will be cached between runs.
Once your image has been built, you’re ready to use it:
docker run -d -p 8080:80 my-react-app:latest
Docker will create a new container using the
my-react-app:latest image. Port 8080 on the host (your machine) is bound to port 80 within the container. This means you can visit
http://localhost:8080 in your browser to see your React project! The
-d flag is present so the container runs in the background.
Switching to NGINX
The example above uses Apache but you can easily switch to NGINX instead.
FROM nginx:alpine COPY --from=build /build/build/ /usr/share/nginx/html
You can adopt alternative web servers in a similar manner; as CRA produces completely static output, you have great flexibility in selecting how your site is hosted. Copy the contents of the
/build/build directory from the
build stage into the default HTML directory of your chosen server software.
Benefits of This Approach
Using Docker to not only encapsulate your final build, but also to create the build itself, gives your project complete portability across environments. Developers only need Docker installed to build and run your React site.
More realistically, this image is ready to use with a CI server to build images in an automated fashion. As long as a Docker environment is available, you can convert your source code into a deployable image without any manual intervention.
By using multi-stage builds, the final image remains streamlined and should be only a few megabytes in size. The much larger
node image is only used during the compilation stage, where Node and npm are necessary.