DEV Community

Cover image for Run a React App in a Docker Container
Peter Jausovec
Peter Jausovec

Posted on • Updated on

Docker React Run a React App in a Docker Container

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:

  1. Install create-react-app
npm install create-react-app --global
Enter fullscreen mode Exit fullscreen mode
  1. Create a new React app:
create-react-app react-docker-app
Enter fullscreen mode Exit fullscreen mode
  1. Go to the react-docker-app folder and run it, to make sure all is good:
cd react-docker-app && yarn start
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jwankhalaf profile image
Jwan Khalaf • Edited

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 in CMD ["serve", "-p", "80", "-s", "."]

Collapse
 
jwankhalaf profile image
Jwan Khalaf

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. :)

Collapse
 
thorakmedichi profile image
Thorak • Edited

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".

Collapse
 
peterj profile image
Peter Jausovec

Is your network connection ok? Are you running behind the proxy?

Collapse
 
justez profile image
Justina

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!

Collapse
 
peterj profile image
Peter Jausovec

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

Collapse
 
harkinj profile image
harkinj

Great article. Any reason for the 2 FROM statements? Thanks.

Collapse
 
peterj profile image
Peter Jausovec

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.

Collapse
 
harkinj profile image
harkinj

Thanks very much fro the info.

Collapse
 
rrmontuan profile image
Ricardo Montuan • Edited

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?

Collapse
 
peterj profile image
Peter Jausovec

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?

Collapse
 
nonbeing profile image
Ambar • Edited

Great article, just faced one problem:

Step 4/9 : RUN yarn run build
 ---> Running in 09a2e8108274
yarn run v1.15.2
$ react-scripts build
/bin/sh: react-scripts: not found
error Command failed with exit code 127.

To solve this, I just added RUN npm install react-scripts -g --silent before the RUN yarn run build in the Dockerfile, and everything ran perfectly then on.

Collapse
 
nickjones profile image
Nick • Edited

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.

Collapse
 
avxkim profile image
Alexander Kim

You forgot a health check, if you're using kubernetes - it's very important.

Collapse
 
peterj profile image
Peter Jausovec

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.

Collapse
 
jitsusama profile image
Joel Gerber

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.

Thread Thread
 
avxkim profile image
Alexander Kim

Aye, you can do that with wget.

Collapse
 
danieldimanov profile image
Daniel Dimanov • Edited

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.

Collapse
 
peterj profile image
Peter Jausovec

Thanks Daniel!

Collapse
 
wansiedler profile image
Alexander Paul Wansiedler

Ok, Thank you.
But where is the hot reload?

Collapse
 
peterj profile image
Peter Jausovec

No hot reload as it's for deployment. I am not using Docker for development in this case :)

Collapse
 
wansiedler profile image
Alexander Paul Wansiedler

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.

Collapse
 
zcilohlegna profile image
zcilohlegna

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 ;)

Collapse
 
kariramour profile image
Amour Karir

Great article! Would be perfect if there was a mention of volumes so that we need not rebuild the image on each code change. Took me a while to find out about that, haha.

Collapse
 
peterj profile image
Peter Jausovec

Thanks Amour! I guess it’s time for me to rewrite the article and add that :)

Collapse
 
hanuz06 profile image
Andrey Li

As I understand in both stages /app directory are not the same?

Collapse
 
peterj profile image
Peter Jausovec

Yes, you're right. In the first stage, the /app folder contains all files due to the COPY . . command. In the second stage, the only thing in the /app folder are the contents of the /app/build folder from stage one (copy --from=builder /app/build .)