Deploying your create-react-app in docker

Deploying your create-react-app in docker

Introduction

For our presentation at the Saltconf 2017 we created a react frontend using react-create-app. In order to run this in docker and autoscale it, we built it into a docker image (check out the apps we made on our saltconf2017 git repo).
 
In this tutorial, we will show you how we built a simple app into two docker images.
 

Objectives

  1. Create an image for development running node where we can quickly develop and test our app.
  2. Create a second image running nginx for production.

 

Prerequisites

For this example we assume:

  • We are running Centos 7.
  • Docker is installed (we use 17.09.0-ce in our example).

 

Step 1 – Install node for the app

curl -sL https://rpm.nodesource.com/setup_9.x | bash -
sudo yum install -y gcc-c++ make
sudo yum install nodejs -y

 

Step 2 – Create the app

Follow along with the create-react-app tutorial until you have a working hello-world app.

npm install -g create-react-app
create-react-app hello-world

 

Step 3 – Create dev dockerfile

Use your favorite text editor to create a file called Dockerfile.dev with the following:

FROM node:9
RUN mkdir /helloworld
WORKDIR /helloworld
COPY hello-world .

RUN npm install --quiet

CMD ["npm", "start"]

EXPOSE 3000

Build with:

[[email protected] ~]$ docker build -t helloworlddev -f Dockerfile.dev .
Sending build context to Docker daemon  184.9MB
Step 1/7 : FROM node:9
9: Pulling from library/node
f49cf87b52c1: Pull complete
7b491c575b06: Pull complete
b313b08bab3b: Pull complete
51d6678c3f0e: Pull complete
da59faba155b: Pull complete
7f84ea62c1fd: Pull complete
1ae6c7e5e8c9: Pull complete
7c07b0a5c6a6: Pull complete
Digest: sha256:a0e9ecaf0519151f308968ab06b001c99753297a6ce1560a69d47e7b1f16926d
Status: Downloaded newer image for node:9
 ---> 3d1823068e39
Step 2/7 : RUN mkdir /helloworld
 ---> Running in a656434f98f2
 ---> a2b2c7b8ff55
Removing intermediate container a656434f98f2
Step 3/7 : WORKDIR /helloworld
 ---> 6428b9b01b5a
Removing intermediate container 400c1a342aa9
Step 4/7 : COPY hello-world .
 ---> cf865e3184f1
Step 5/7 : RUN npm install --quiet
 ---> Running in 95fce6cd0da4
added 115 packages in 11.854s
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

 ---> b1da4529c2ea
Removing intermediate container 95fce6cd0da4
Step 6/7 : CMD npm start
 ---> Running in 4fd33d06db82
 ---> eb4bcddeb395
Removing intermediate container 4fd33d06db82
Step 7/7 : EXPOSE 3000
 ---> Running in 5199d4ee0b2c
 ---> ee33dc76a35a
Removing intermediate container 5199d4ee0b2c
Successfully built ee33dc76a35a
Successfully tagged helloworlddev:latest

Let’s get this container running with the following:

[[email protected] ~]$ docker run -d --name helloworlddev -p 3000:3000 helloworlddev:latest
80e39ef208db1fbb15b02cbf49118ea72d39cd305cd3d4283b7c8321969fd941

Let’s make sure the container is running:

[[email protected] ~]$ docker ps
CONTAINER ID        IMAGE                  COMMAND             CREATED             STATUS              PORTS                    NAMES
80e39ef208db        helloworlddev:latest   "npm start"         3 seconds ago       Up 2 seconds        0.0.0.0:3000->3000/tcp   helloworlddev

We can also see the size of the container:

[[email protected] ~]$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED              SIZE
helloworlddev        latest              5db6aba3402f        About a minute ago   810MB
node                 9                   3d1823068e39        3 weeks ago          676MB

Note our container size for helloworlddev is 810MB.
 

Step 4 – Create prod dockerfile

Now let’s create a ‘production’ container. Let’s make a Dockerfile with the following:

FROM node:9 as builder
RUN mkdir /helloworld
WORKDIR /helloworld
COPY hello-world .

RUN npm install --quiet
RUN npm run build

# Copy built app into nginx container
FROM nginx:1.13.5
COPY --from=builder /helloworld/build /usr/share/nginx/html

EXPOSE 80

Build with:

[[email protected] ~]$ docker build -t helloworld -f Dockerfile.prod .
Sending build context to Docker daemon  184.9MB
Step 1/9 : FROM node:9 as builder
 ---> 3d1823068e39
Step 2/9 : RUN mkdir /helloworld
 ---> Using cache
 ---> a2b2c7b8ff55
Step 3/9 : WORKDIR /helloworld
 ---> Using cache
 ---> 6428b9b01b5a
Step 4/9 : COPY hello-world .
 ---> Using cache
 ---> cf865e3184f1
Step 5/9 : RUN npm install --quiet
 ---> Using cache
 ---> b1da4529c2ea
Step 6/9 : RUN npm run build
 ---> Running in f0a0bece48f9

> [email protected] build /helloworld
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  35.14 KB  build/static/js/main.d66de642.js
  177 B     build/static/css/main.f7a92a2d.css

The project was built assuming it is hosted at the server root.
To override this, specify the homepage in your package.json.
For example, add this to build it for GitHub Pages:

  "homepage" : "http://myname.github.io/myapp",

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

 ---> d4f928fa7ae4
Removing intermediate container f0a0bece48f9
Step 7/9 : FROM nginx:1.13.5
1.13.5: Pulling from library/nginx
bc95e04b23c0: Pull complete
110767c6efff: Pull complete
f081e0c4df75: Pull complete
Digest: sha256:004ac1d5e791e705f12a17c80d7bb1e8f7f01aa7dca7deee6e65a03465392072
Status: Downloaded newer image for nginx:1.13.5
 ---> 1e5ab59102ce
Step 8/9 : COPY --from=builder /helloworld/build /usr/share/nginx/html
 ---> f55ef73c3580
Step 9/9 : EXPOSE 80
 ---> Running in 722d18289b70
 ---> e635eb890b32
Removing intermediate container 722d18289b70
Successfully built e635eb890b32
Successfully tagged helloworld:latest

Let’s start our production container:

docker run -d --name helloworld -p 80:80 helloworld

Note the difference in sizes:

[[email protected] ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
helloworld          latest              e635eb890b32        59 seconds ago       109MB
helloworlddev       latest              ee33dc76a35a        5 minutes ago        810MB
node                9                   3d1823068e39        4 days ago           676MB
nginx               1.13.5              1e5ab59102ce        2 months ago         108MB

helloworlddev is 810MB while helloworld is only 109MB!
 

Differences between the Two Containers

Now that the containers are created, we can take a closer look inside:

[[email protected] ~]$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                          NAMES
0c2772795c51        helloworld          "nginx -g 'daemon ..."   2 seconds ago       Up 1 second         80/tcp, 0.0.0.0:80->80/tcp   helloworld
bccd86a60a9c        helloworlddev       "npm start"              44 seconds ago      Up 43 seconds       0.0.0.0:3000->3000/tcp         helloworlddev

We can easily go into the helloworlddev container to edit and test things quickly:

[[email protected] ~]$ docker exec -it helloworlddev /bin/bash
[email protected]:/helloworld# apt-get update; apt-get install nano
[email protected]:/helloworld# nano src/App.js

The helloworld container, however, is difficult to edit and perform tests with:

[[email protected] ~]$ docker exec -it helloworld /bin/bash
[email protected]:/# cat /usr/share/nginx/html/static/js/main.d66de642.js

 

Conclusion

I hope this helps users of create-react-app see how easy it is to create dev and prod docker images.