Here are simple steps that show you how to start with an empty React app (using create-react-app), create a production build of that app and then run it inside a Docker container.
Let's start by creating a new React app:
- Install create-react-app
npm install create-react-app --global
- Create a new React app:
create-react-app react-docker-app
- Go to the
react-docker-app
folder and run it, to make sure all is good:
cd react-docker-app && yarn start
The
yarn start
command compiles the React app and opens the browser.
Now that we have the app running let's create a Dockerfile
in the root folder of the project. Here are the contents of the Dockerfile
:
Before we continue, let's explain what's happening in this Dockerfile
.
Lines 1-4 are the first stage of the build. In this stage, you copy all source code to the container and execute yarn run build
that creates an optimized production build.
Lines 6-10 are the second stage for the build. You install the serve package and on line 9, you copy the output from the first stage of the build from the folder /app/build
to the current folder in the container (/app
- this gets set by the WORKDIR /app
instruction in the Dockerfile
).
About multi-stage builds: If you're wondering about two FROM statements in the Dockerfile. This is because you want to use a multi-stage build. In the first stage of the build, you copy the source code to the container and run the build command. In the second build stage, you copy only the built artifacts (HTML, JS, ...) to the container. Using multi-stage build results in a significantly smaller Docker image. The first image in the example is ~198MB, while the second one is only 86.7 MB.
With the last line, you run the serve
command to serve the contents of the current folder on port 80
.
Instead of serve, you could also use Nginx; however that might require a bit more config.
To build the image, you can run the following command from the project root folder, where your Dockerfile
is:
docker build -t react-docker-app .
With the -t
you specify the name of the image, and with the .
you specify the build context (e.g. the current folder). When the build completes, the last line should look something like this:
Successfully tagged react-docker-app:latest
Finally, let's run this container now. To run it locally, you need to provide the name of the image and the port we want the React app to be accessible on. Note that we used port 80
in the serve command, so need to make sure to use 80
when specifying the container port like this:
docker run -it -p 8080:80 react-docker-app
Once the container is running, you can open http://localhost:8080
and you'll be able to access the React app running inside the Docker container.
🔥 If you want to learn more about Kubernetes, Istio, Docker, and cloud-native in general, check out the my Learn Istio ebook 📖. You can get a free preview of the book at 👉 https://learnistio.com 👈
Top comments (27)
Thanks for the article. I have followed along and all seems to build fine. However when I run the image and navigate to
localhost:8080
I get a blank page with the text Error: Failed to fetch.Any ideas on why this might be happening?
Also, what does the
-s
do inCMD ["serve", "-p", "80", "-s", "."]
Ignore the first part of my question. The
Error: Failed to fetch
is what my ReactJS app does when it can't mak calls to the API. :)When trying to build I am getting this error
[1/4] Resolving packages...
error An unexpected error occurred: "registry.yarnpkg.com/react: getaddrinfo EAI_AGAIN registry.yarnpkg.com registry.yarnpkg.com:443".
Is your network connection ok? Are you running behind the proxy?
Hi there Peter!
I believe you made a mistake there at line #3 in the Dockerfile. You should copy files in project directory to your workdirectory which is /app. Now you declared
COPY . .
that is copying files to the same directory.Cheers!
Hi Justina!
In the line above that one, I am setting the working directory (inside the container) to /app (
WORKDIR /app
) and then copying everything from build context root (which is the current folder on my host machine in this case:.
) to the target folder inside the container. Since the working directory is set to/app
the.
in the COPY instruction copies everything to that folder.If I removed the WORKDIR, I would also need to update the COPY instruction to read
COPY . /app
Thanks,
Peter
Great article. Any reason for the 2 FROM statements? Thanks.
That's a great question! It has to do with the multi-stage build in Docker.
In the first FROM statement (first stage), you copy the source code to the container and run the build command (
yarn run build
). The build command generates everything that you need to run the app (JS, HTML, etc.)In the second FROM statement (second stage of the build), on line 9, you copy the contents of the /app/build folder from the first container to the current one.
So the second image actually contains the built artifacts (HTML, JS, ...) and the serve package - it does not contain any of the node_modules or source code either.
Another advantage of using multi-stage builds is the size. The first image in the example is ~198MB, while the second one is only 86.7 MB.
Hope this explains it - I'll update the article to clarify this more as well.
Thanks very much fro the info.
Is it common to run the create-react-app command on the host machine or there's another way to do that (I mean inside the container)? We also have to consider that we could need some other dependencies inside our package.json. Do you know how to deal with that?
Usually, you would run create-react-app once, outside of the container on the host machine, just to create your app.
yarn build command takes care of the dependencies - on line 3 I am copying everything (package.json included) to the builder container, then yarn build does its magic and takes care of any dependencies that are needed.
Does that answer your questions?
Great article, just faced one problem:
To solve this, I just added
RUN npm install react-scripts -g --silent
before theRUN yarn run build
in the Dockerfile, and everything ran perfectly then on.I had the same issue because I use don't use yarn. Solved by deleting node-modules, package-lock.json, and run the command yarn (this is similar to npm install). Then as he stated up above test the installation with yarn start.
You forgot a health check, if you're using kubernetes - it's very important.
curious to hear how would that look like for a static website? If this was a backend service, I’d agree that you should probably have a /health or a similar endpoint; for static website, it doesn’t make much sense.
An HTTP health check would be good for Kubernetes, just simply visiting / to validate that serve is actually serving up pages, but that doesn't require adding a HEALTHCHECK line to your Dockerfile. Kubernetes ignores HEALTHCHECK statements and instead requires you to define this manually in your Pod/Deployment/... spec section. Your Dockerfile is fine as is for Kubernetes. If someone was going to deploy it directly via Docker, or use Swarm to orchestrate, then you'd want a HEALTHCHECK statement in your Dockerfile.
Aye, you can do that with
wget
.Amazing! Works like a charm! Thank you very much! Fast, easy, powerful and SO helpful! Thank you again! I'm using node 12, so I just changed 11 to 12 and of course the container name.
Thanks Daniel!
Ok, Thank you.
But where is the hot reload?
No hot reload as it's for deployment. I am not using Docker for development in this case :)
Please explain how can I run the code in a docker in the browser and debug it at the same time.
Given, I would like to send all react code through nginx proxy.
Thanks a lot Peter, this helps me a lot. I just do some changes in your dockerfile to:
COPY . .
RUN yarn run build
RUN npm install
RUN npm run build
I'm not so sure why I need to do npm command twice, but there are too many discussion about these two differeneces, so I just drop it here in case somebody out there prefer to use npm rather than yarn for the build command
PS: Peter, I haven't found your article about dockerized Go project ;)