Running a React app in a Docker container

I started converting my AngularJS AddressBook app into React. Since this is part of a larger project to run all the parts of my app in Docker containers with Docker Compose, I needed to look at how I can run my React app in a container.

For development, using the webpack dev server works well locally. Running this from within a container though requires doing an ‘npm install’ and a ‘npm run build’ inside the container, which probably doesn’t make sense to run every time, as with all the npm dependencies from create-react-app this take some time to run.

The other option is to build the app locally and then just build a container with the webpack output, and serve it with an HTTP server like nginx.

Let’s take a look at each of these options.

The easiest and simplest approach is to build the app locally first, as you normally would:

npm install
npm run build

A Dockerfile to build the container using the output from ‘npm run build’ then looks like (using an nginx image as a starting point):

FROM nginx
COPY build /usr/share/nginx/html

You build this with:

docker build -t addressbook-react-web-nginx .

And then run with:

docker run -it -p 80:80 addressbook-react-web-nginx

Next up, for a comparison, the Dockerfile to push everything into the image and run with ‘npm run start’ inside the container would look like:

FROM node:6.10-alpine
RUN mkdir -p /home/web
COPY *.html /home/web/
COPY public /home/web/public/
COPY src /home/web/src/
COPY *.json /home/web/
WORKDIR "/home/web"
RUN npm install
ENTRYPOINT npm run start

This is starting from one of the official node images. It copies in all the source files, runs npm install and then sets the entrypoint for when the container starts to run the webpack dev server.

An improvement on this approach could be to build an intermediate container with the package.json and npm install already run, and then build new images from this incrementally adding just the changed source files. Plenty of options to explore depending on what you need to do.

Next up, modify my previous docker compose file to include one of these new images containing my React app and now I’ve got all the pieces ready to go.

Converting my AngularJS AddressBook app to React

Several months back I spent some time looking at Docker and Docker Compose, and put together a sample AngularJS web app served by Nginx in one container, against a Spring Boot JAX-RS RESTful backend in another container, and using MongoDB in another container.

I wrote a couple of articles (part 1, and part 2) describing the Docker containers and how they were configured together using Docker Compose, but I didn’t spend much time talking about the web app itself. I was intending at the time that I’d develop the frontend app using a number of frameworks as a comparison. The AngularJS AddressBook app is functional (on GitHub here), I got part way converting it to an Angular 2 based app (on GitHub here, although clearly I was unsure at the time thinking Angular 2 was called AngularJS2), but the React app I made a rough start at but didn’t get very far.

Given most recently I’ve been spending some time getting up to speed with some React, I’m going to pick up this app again and complete it, so I’ll have an interesting side by side comparison of the same app developed with all three frameworks. More updates to come.

Getting past Vagrant’s “Authentication failure” error when starting up OpenShift Origin

For getting up and running quickly with OpenShift Origin, RedHat have an all-in-one VM image you can provision with Vagrant. The instructions mention to not use Vagrant 1.8.5 as there’s an issue with the SSH setup – since I already had 1.8.5 installed for some other projects, I tried anyway, and ran into issues with SSH’ing into the VM with SSH keys.

When provisioning the VM, you’ll see:

 

Kevins-MacBook-Pro:openshift-origin kev$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Checking if box 'openshift/origin-all-in-one' is up to date...
==> default: Resuming suspended VM...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Authentication failure. Retrying...
    default: Warning: Authentication failure. Retrying...

There’s a number of posts discussing this issue and a few workarounds, for example, here and here.

The suggestions relate to switching from the ssh authentication to userid/password, by adding this to your Vagrantfile:

config.ssh.username = "vagrant"
config.ssh.password = "vagrant"

I tried this, and when running vagrant up I had different errors about “SSH authentication failed”. Next I tried adding this recommendation:

 

config.ssh.insert_key = false

This didn’t make any difference initially, but doing a vagrant destroy, and then trying to bring it up again initially ran into the same issue, I Ctrl-C’d out and tried again and then it worked second time. I’m not sure what steps got past the ssh keys issue, but at this point I was up and running. There’s a long discussion in both the linked threads above that describe the cause of the issue, so if you’re interested take a look through those threads.

Building a multi-container Spring Boot and MongoDB webapp with Docker 1.12.x – part 2

In the first part of this article, I showed how I split the frontend, backend and database all into their own containers, and how each could be individually scaled using docker-compose.

If you’re already familiar with docker-compose and using haproxy for load balancing against container, you might have noticed there’s a limitation in my approach, as both the backend REST service was exposing it’s port 8080 externally, and so I don’t think HTTP requests from the frontend browser in the app were ever passing through the haproxy to be load balanced, only the requests to load the front end on port 80 were being load balanced.

I looked into how I could configure haproxy with multiple backends, listening on different ports, but eventually came to the conclusion that adding two different haproxy containers, one load balancing for port 80 and one for port 8080 was easy to do.

I’m not sure if this is the best way to approach this, but it certainly. works. Leave me a comment if you have any suggestions.

Here’s my final docker-compose.yml:

 

version: '2'
  
services:
    mongodata:
        image: mongo:3.2
        volumes:
        - /data/db
        entrypoint: /bin/bash
    mongo:
        image: mongo:3.2
        depends_on: 
            - mongodata
        volumes_from:
            - mongodata
        ports:
            - "27017"
    addressbook:
        image: addressbook
        depends_on: 
            - mongo
        environment:
            - MONGODB_DB_NAME=addressbook
        ports:
            - "8080"
        links:
            - mongo
    web:
        image: docker-web-angularjs
        ports:
            - "80"
    lb-web:
        image: dockercloud/haproxy
        depends_on: 
            - web
        environment:
            - STATS_PORT=1936
            - STATS_AUTH="admin:your-password"
        links:
            - web
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
        ports:
            - 80:80
            - 1936:1936
    lb-addressbook:
        image: dockercloud/haproxy
        depends_on: 
            - addressbook
        environment:
            - STATS_PORT=1937
            - STATS_AUTH="admin:your-password"
        links:
            - addressbook
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
        ports:
            - 8080:80
            - 1937:1937