Configuring Dockerfiles to build Docker Images

Configuring Dockerfiles to build Docker Images

by John Vincent


Posted on June 2, 2021


Dockerfiles are used to build Docker images. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.

This is part of a series of discussions regarding Deploying TaskMuncher, a React and Node application, to a Multi-Container Docker Environment at AWS using Dockerhub and Travis CI

For more details, please see Overview of Create Multi-Container Docker TaskMuncher Application at AWS

Overview

Let's start by describing the systems that are to be created.

TaskMuncher AWS Production Docker Containers TaskMuncher Development Docker Containers TaskMuncher Production in Development Docker Containers

Dockerfiles

It may be seen from the following there are a number of Dockerfiles.

Config Folder Architecture

It should be noted that images are required for dev, devprod and aws. There are a number of images that are used by all environments.

List of Dockerfile sections

Dockerfiles for all Environments

This section will cover the general usage images.

Dockerfile for MongoDB

./config/mongodb/Dockerfile

# Set the base image to Ubuntu
FROM ubuntu:20.04

# Update the repository sources list and install required packages
RUN apt-get update && apt-get install -y nginx curl gnupg2 wget

RUN curl -sL https://deb.nodesource.com/setup_12.x  | bash -

RUN apt-get -y install nodejs

# get mongodb list
RUN wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -

RUN echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list

# Update the repository sources list
RUN apt-get update

# Install MongoDB package (.deb)
RUN apt-get install -y mongodb-org

# Create the default data directory
RUN mkdir -p /data/db

# Expose the default port
EXPOSE 27017

# Default port to execute the entrypoint (MongoDB)
CMD ["--port 27017"]

# Set default container command
ENTRYPOINT /usr/bin/mongod --bind_ip 0.0.0.0

This creates an image that has MongoDB installed onto Ubuntu 20.04.

Dockerfile for mongo-client

./config/mongo-client/Dockerfile

# Set the base image to Ubuntu
FROM ubuntu

# Update the repository sources list and install required packages
RUN apt-get update && apt-get install -y nginx curl gnupg2 wget

RUN curl -sL https://deb.nodesource.com/setup_12.x  | bash -

RUN apt-get -y install nodejs

# get mongodb list
RUN wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -

RUN echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list

# Update the repository sources list
RUN apt-get update

# Install MongoDB package (.deb)
RUN apt-get install -y mongodb-org

# Create the default data directory
RUN mkdir -p /data/db

# Expose the default port
EXPOSE 27017

#
# Application specific commands follow
#

# use /app for our application
WORKDIR "/app"

# npm install only needs package.json
COPY server/package.json ./
COPY server/package-lock.json ./

# install npm modules
RUN npm install

# copy code
COPY server ./

# copy start file
COPY config/mongo-client/start-command ./start-command

RUN mkdir -p ./logs

CMD ["./start-command"]

This creates an image

  • based on Ubuntu
  • with Mongodb installed so the mongo client can be used to access the mongodb container described above
  • with the application server software installed
  • which will run ./start-command when the container starts.

The Dockerfile references ./config/mongo-client/start-command

#!/bin/sh
#
echo "Starting 30 seconds of sleep"
sleep 30

cd scripts

echo "Checking if data is already loaded"

abc=`mongo mongodb:27017/taskmuncher-2021 is-any-data.js`
TEXT=`echo $abc | awk -F'-----' '{print $2}'`
echo "TEXT $TEXT"

if [ "$TEXT" = "Data Added" ]; then
	echo "Data has already been loaded"
else 
	echo "Data has NOT been loaded"
	echo ""
	echo "Loading Mongo Taskmuncher data"
	mongo mongodb:27017/taskmuncher-2021 all-data.js
	echo "Loaded Mongo Taskmuncher data"
fi

echo "Completed mongo-client:start-command"

which is just a way of seeding the MongoDB database.

For details

mongo mongodb:27017/taskmuncher-2021 all-data.js

means

  • using mongo client
  • connect to service mongodb at port 27017
  • and run script all-data.js

It is very important to notice that service mongodb actually refers to the host mongodb which I have configured to be the MongoDB database for this application.

Dockerfile for nginx

./config/nginx/Dockerfile

FROM nginx

EXPOSE 8100

COPY ./config/nginx/default.conf /etc/nginx/conf.d/default.conf
  • FROM nginx - get latest version of image nginx from Dockerhub
  • EXPOSE 8100 - open port 8100
  • copy nginx configuration file to nginx image

The nginx server configuration file ./config/nginx/default.conf

upstream client {
  server client:8040;
}

upstream api {
  server api:3110;
}

server {
  listen 8100;

  location / {
    proxy_pass http://client;
  }

  location /sockjs-node {
    proxy_pass http://client;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
  }

  location /api {
    proxy_pass http://api;
  }
}
  • listen 8100 - nginx is listening on port 8100

Requests to /api will be proxied to

upstream api {
  server api:3110;
}

which is hostname api on port 3110, which corresponds with

   {
      "name": "server",
      "image": "johnvincentio/taskmuncher:server",
      "hostname": "api",
      "essential": false,
			"links": ["mongodb"],
      "memory": 128
    },

in dockerrun.aws.json, see Create Dockerrun.aws.json file for AWS for details,

and

  api:
    image: taskmuncher-api-dev
    depends_on:
      - mongodb
    build:
      dockerfile: ./config/server/dev/Dockerfile
      context: .

in docker-compose-*.yml, see Configuring Makefiles to build and run Docker Images using Docker, Docker Compose and Dockerfile for details.

Requests to / will be proxied to

upstream client {
  server client:8040;
}

which is hostname client on port 8040, which corresponds with

    {
      "name": "client",
      "image": "johnvincentio/taskmuncher:client",
      "hostname": "client",
      "essential": false,
      "memory": 128
    },

in dockerrun.aws.json, see Create Dockerrun.aws.json file for AWS for details,

and

  client:
    image: taskmuncher-client-dev
    stdin_open: true
    build:
      dockerfile: ./config/client/dev/Dockerfile
      context: .

in docker-compose-*.yml, see Configuring Makefiles to build and run Docker Images using Docker, Docker Compose and Dockerfile for details.

Note that nginx/default.conf can reference another container by using the service name in docker-compose-*.yml.

Dockerfile for dockercfg

AWS needs a .dockercfg file to allow AWS to authenticate with a Dockerhub private repository.

This image is created so that, when run in a Docker container, a .dockercfg file may be generated.

For details, see Allow AWS to authenticate with a Dockerhub private repository

./config/dockercfg/Dockerfile

# Set the base image to Ubuntu
FROM ubuntu:20.04

# https://docs.docker.com/engine/install/ubuntu/

# Update the repository sources list
RUN apt-get update
RUN apt-get install -y curl apt-transport-https ca-certificates gnupg-agent software-properties-common

RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

#
RUN apt-get update
RUN apt-get install -y docker-ce docker-ce-cli containerd.io

# use /app for our application
WORKDIR "/app"

# copy start file
COPY config/dockercfg/start-command ./start-command

# copy example cfg file
COPY config/dockercfg/examplecfg.txt ./examplecfg.txt

# copy docker password file
COPY config/dockercfg/my_password.txt ./my_password.txt

CMD ["./start-command"]

This creates an Ubuntu 20.04 image that will execute ./start-command when the container is started.

./config/dockercfg/start-command

#!/bin/sh
#

cd /app

USERNAME="johnvincentio"
cat ./my_password.txt | docker login --username $USERNAME --password-stdin

cat /root/.docker/config.json

cat ./examplecfg.txt

echo "*** Build your own .dockercfg file from the above ***\n"

Development Dockerfiles

Development Dockerfiles are required for client and server

Development Dockerfile for Client

./config/client/dev/Dockerfile

FROM node:15

# use /app for our application
WORKDIR "/app"

# npm install only needs package.json
COPY client/package.json ./
COPY client/package-lock.json ./

# install npm modules
RUN npm install

# copy code
COPY client ./

# copy env file
COPY config/client/dev/client.env ./.env

# copy start file
COPY config/client/dev/start-command ./start-command

# webpack is configured to use port 8040
EXPOSE 8040

# run this command when the container starts
CMD ["./start-command"]
  • FROM node:15 - get version 15 of image node from Dockerhub
  • WORKDIR "/app" - everything copied to the image will be copied to /app
  • COPY client/package.json ./ - copy named file to /app
  • RUN npm install - execute this command on the image now
  • COPY client ./ - copies all files from local file system ./client to /app except for files listed in .dockerignore
  • EXPOSE 8040 - open port 8040
  • CMD ["./start-command"] - provides a script that will be run when the Docker container starts

partial ./config/client/dev/client.env

NODE_ENV=development

HOME_HOST=0.0.0.0
HOME_PORT=8040

HOME_URL=http://localhost:8100

SERVER_APIS_URL=http://localhost:8100

./config/client/dev/start-command

#!/bin/sh
#

export ENV_FILE=$(pwd)/.env

node_modules/.bin/webpack serve

which means

  • set environment variable ENV_FILE to ./env
  • run webpack

Development Dockerfile for Server

./config/server/dev/Dockerfile

FROM node:15

# use /app for our application
WORKDIR "/app"

# npm install only needs package.json
COPY server/package.json ./
COPY server/package-lock.json ./

# install npm modules
RUN npm install

# copy code
COPY server ./

# copy env file
COPY config/server/dev/server.env ./.env

# copy start file
COPY config/server/start-command ./start-command

RUN mkdir -p ./logs

EXPOSE 3110

CMD ["./start-command"]

which references ./config/server/start-command

#!/bin/sh
#

ENV_FILE=./.env node server.js

which means

  • set environment variable ENV_FILE to ./.env
  • execute node server.js

Partial ./config/server/dev/server.env

HOME_URL=http://localhost:8100

PORT=3110

WHITE_LIST=http://localhost:8100

DATABASE_URL='mongodb://mongodb:27017/taskmuncher-2021'
  • HOME_URL is the URL of the Client Application
  • PORT - server listening on this port
  • WHITE_LIST - CORS URL white list
  • mongodb:27017 - the hostname and port of the MongoDB service

Production in a Development Dockerfiles

Production in a Development Dockerfiles are required only for client

Production in a Development Dockerfile for Client

./config/client/devprod/Dockerfile

FROM node:15 as builder

# use /app for our application
WORKDIR "/app"

# npm install only needs package.json
COPY client/package.json ./
COPY client/package-lock.json ./

# install npm modules
RUN npm install

# copy code
COPY client ./

# copy env file
COPY config/client/dev/client.env ./.env

# build react app
RUN npm run build:actual:production

# handle nginx server
FROM nginx
EXPOSE 8040
COPY config/client/aws/nginx/default.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /app/dist /usr/share/nginx/html
  • FROM node:15 as builder - use version 15 of image node from Dockerhub and assign a name builder for later use.
  • RUN npm run build:actual:production - references a script in package.json that does "build:actual:production": "rm -rf dist && webpack --mode production --progress", building the react production code.
  • FROM nginx - use latest version of nginx
  • EXPOSE 8040 - open port 8040
  • Copies nginx configuration to nginx image
  • /app/dist - contains the production react files that were constructed using RUN npm run build:actual:production
  • COPY --from=builder - copy from the node distribution to /usr/share/nginx/html which is in the context of FROM NGINX

The nginx server configuration file config/client/aws/nginx/default.conf

server {
	listen 8040;

	location / {
		root /usr/share/nginx/html;
		index index.html index.htm;
		try_files $uri $uri/ /index.html;
	}
}

is listening on port 8040 and distributing file from /usr/share/nginx/html, which is where the production react files were just placed.

AWS Production Dockerfiles

Development Dockerfiles are required for client and server

AWS Production Dockerfile for Client

./config/client/aws/Dockerfile

FROM node:15 as builder

# use /app for our application
WORKDIR "/app"

# npm install only needs package.json
COPY client/package.json ./
COPY client/package-lock.json ./

# install npm modules
RUN npm install

# copy code
COPY client ./

# copy env file
COPY config/client/aws/client.env ./.env

# build react app
RUN npm run build:actual:production

# handle nginx server
FROM nginx
EXPOSE 8040
COPY config/client/aws/nginx/default.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /app/dist /usr/share/nginx/html
  • FROM node:15 as builder - use version 15 of image node from Dockerhub and assign a name builder for later use.
  • RUN npm run build:actual:production - references a script in package.json that does "build:actual:production": "rm -rf dist && webpack --mode production --progress", building the react production code.
  • FROM nginx - use latest version of nginx
  • EXPOSE 8040 - open port 8040
  • Copies nginx configuration to nginx image
  • /app/dist - contains the production react files that were constructed using RUN npm run build:actual:production
  • COPY --from=builder - copy from the node distribution to /usr/share/nginx/html which is in the context of FROM NGINX

The nginx server configuration file config/client/aws/nginx/default.conf

server {
	listen 8040;

	location / {
		root /usr/share/nginx/html;
		index index.html index.htm;
		try_files $uri $uri/ /index.html;
	}
}

is listening on port 8040 and distributing file from /usr/share/nginx/html, which is where the production react files were just placed.

Partial ./config/client/aws/client.env

NODE_ENV=production

HOME_HOST=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com
HOME_PORT=80

HOME_URL=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com

SERVER_APIS_URL=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com

The URL of the application is http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com.

This URL was assigned when the application was created in AWS Elastic Beanstalk. For details, see Configure AWS for a Multi-Container Docker Application.

The URL is the same for the React client application and for the Node/Express application as Nginx is listening on port 80 and proxying requests to either the React Docker container or to the Node/Express Docker container based on the URL of the request.

AWS Production Dockerfile for Server

./config/server/aws/Dockerfile

FROM node:15

# use /app for our application
WORKDIR "/app"

# npm install only needs package.json
COPY server/package.json ./
COPY server/package-lock.json ./

# install npm modules
RUN npm install

# copy code
COPY server ./

# copy env file
COPY config/server/aws/server.env ./.env

# copy start file
COPY config/server/start-command ./start-command

RUN mkdir -p ./logs

EXPOSE 3110

CMD ["./start-command"]

./config/server/start-command

#!/bin/sh
#

ENV_FILE=./.env node server.js

which means

  • set environment variable ENV_FILE to ./.env
  • execute node server.js

Partial ./config/server/aws/server.env

HOME_URL=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com

PORT=3110

WHITE_LIST=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com

DATABASE_URL='mongodb://mongodb:27017/taskmuncher-2021'
  • HOME_URL is the URL of the Client Application
  • PORT - server listening on port 3110
  • WHITE_LIST - CORS URL white list
  • mongodb:27017 - the hostname and port of the MongoDB service

The URL of the application is http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com.

This URL was assigned when the application was created in AWS Elastic Beanstalk. For details, see Configure AWS for a Multi-Container Docker Application.

The URL is the same for the React client application and for the Node/Express application as Nginx is listening on port 80 and proxying requests to either the React Docker container or to the Node/Express Docker container based on the URL of the request.