Docker – Part 3 – Multiple Containers

By Huntly Cameron Docker

Now that we’ve got a simple PHP API running, lets look at how we can consume that.

For this, we’re going to create a small react app using RSBuild

There’s no particular reason for RSBuild over anything else other than I wanted to try it out!

React App

Check out the code on this repo: React API Consumer

Like the API, there’s not much going on here, just a button click that calls our API and updates the heading text.

Like the api, first we build the image

docker build -t php-simple-api-react .

Then we can run the container, again mapping the ports from our host machine, to the container:

docker run -p3000:3000 --name react-app php-simple-api-react

note: If you’ve not already done so, you’ll need to make sure you have the php api up and running!

docker run -p8080:8080 --name php-api php-simple-api

Note: docker may complain about already having a container. If that’s the case then just run docker start php-api

You should now be able to open your browser and navigate to http://localhost:3000

Cleaning house

Very quickly you’ll start spinning up plenty of containers. It’s worth at this point exploring how we can keep things tidy.

You should have two containers running, how do we see whats going on? We can use docker ps to show all running containers

You should see two (or more) rows in the output table. The last column, names, is what we’re interested in. You can go ahead and run the following commands to stop both the API and react containers:

docker stop php-api
docker stop react-app

If you run docker ps again, you’ll not see these two containers running.

To show all containers on our system, even those that are not running, we can run the docker ps -a command.

Removing containers

You can remove containers by using the docker rm command, which is just a short hand way for saying docker container rm as you’ll do this quite a lot.

Feel free to run the following commands to clear out our API and react containers:

docker rm php-api
docker rm react-app

Auto remove on stop

Stopping and then having to remove containers can get old pretty quickly when we’re experimenting, so we can tell docker to remove our containers for us after they are finished running by providing the --rm flag to the docker run command, like so:
docker run --rm -d -p 8080:8080 --name php-api php-simple-api

Keeping containers lean, and clean

Keen readers will notice if they inspect the Dockerfile on the react app that we’re doing some shady stuff by running the react app in preview mode inside the image.

Lets fix this. Go grab the new version of the react app at [GH repo](). You can delete the repo you downloaded at the start of this part if you are following along.

The important part is the new additions to the Dockerfile for this project. Lets take a look:

# First image, does the rspec build
FROM node:lts-alpine as builder

WORKDIR /usr/local/share/

COPY package.json .
COPY package-lock.json .

RUN npm install

COPY . .

RUN npm run build


# second image, a nginx container to host the project
FROM nginx as final

COPY --from=builder /usr/local/share/dist /usr/share/nginx/html

EXPOSE 80

You can build this as you normally would be navigating to the directory that contains this Dockerfile and run the following:

docker build -t react-slim .

Once built you can run it via the following command:

docker run --rm -d -p 80:80 --name react react-slim

Note: now we’re using nginx we run it on port 80. However if you want to keep it so that you run localhost:3000 change the host part of the -p flag. e.g. -p 3000:80

What we’ve done here is to split our container creation into two steps.

  1. The first image installs all dependancies, then copies the source material, then runs the build.
    1. We do it this way so that we can cache our dev dependancies between image builds. Simple source edits won’t require full rebuilds.
  2. The second image is a plain NGINX image that we’ll host the app on. We take the build output of the first image (the /dist/ folder) and copy it across to the NGINX image.

This is a good idea for a few reasons:

  1. We’re running our app properly and not on the preview server.
  2. By not having all the dependencies on the final image, we reduce the size and attack surface area of the image.

For comparison, here is the size difference between the single step build and the multi step build:

  • Single – 408MB
  • Multi – 193MB

Multi step build come into their own when working with compiled languages or complex JavaScript apps with lots of dev dependencies and tooling required to lint, test, and compile/build the application.

The can still be useful on interpreted languages like PHP and Python to ensure you’re not running anything that you don’t need in production.