feat. Advanced Docker - Images
In this article of the feat. series, I'll highlight a key component of Docker - Images. A docker image is to container what a java class is to an object. In other words, a container is defined as a runtime instance or version of an image. In Container101 article, we pulled a pre-built docker image from docker hub and built/ran container of it. This, being an advanced article, will talk about creating a docker image from scratch ??.
Let's put an image under microscope using history & inspect to see what's it made of:
# Check available docker images on your machine
docker image ls
# Show layers of changes made to the image. Replace 'nginx:alpine' with any
# other image-repo:tag or image id on your machine
docker image history nginx:alpine
# Show detailed information on an image
docker image inspect nginx:alpine
The command history shows all changes made to that image. Images are built using layering concept where every image starts with a blank layer called scratch. Then every change made to that image creates a layer. Some layers might mean a metadata change whereas others might add files to the image. This can be observed from the SIZE column which shows size changes for a particular layer. The inspect, on the other hand, shows further details about the image which you can check-out by running it on any of the docker images on your machine.
The main building block of Docker images is the Dockerfile. Per Docker Official Docs, a Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. We'll be using this github repo to create an image for a simple nodejs app (called 'dockerizing' the application). The app prints out Hello World from the absolute URL of the machine it runs on as well as a static version #. Let's go over the Dockerfile to understand how it works:
# Getting the base image for this simple node application
FROM node:12-alpine
# Adding maintainer info. This is optional
MAINTAINER Dewan Ahmed ([email protected])
# Create app directory and use it as working directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
# Getting the package manager
RUN npm install
# Bundle app source
COPY . /usr/src/app
# Telling docker which file to use to start this application
CMD ["node", "index.js"]
# Opening up this port on the running container
EXPOSE 3000
The FROM command tells docker which base image to choose for building our custom image. This is the layering concept we mentioned about where you start off some pre-built image from Docker Hub. MAINTAINER is an optional tag. In the following commands, we create a directory and copy the app data and dependencies to that directory. Finally, we initiate the RUN and EXPOSE command to run the application and open that specific port on that docker container.
If you haven't already, clone the above git repo and run docker image build -t simple-nodejs-app . on the base directory. This will build a docker image for your project. If you make a small code-change and re-run the same command, you'll see a number of ---> Using cache lines on your terminal which signifies the caching principle of docker images. Docker only fetches the changes from Docker Hub and uses cache to build images because each binary layer is exactly the same as the previous build layer. And, it only takes fractions of the original time to build subsequent images because all processes install dependencies using cache. In this example, as the build only copies the source code diff to the docker images, this significantly improves build and deploy time (crucial for production).
Now that we have our image, let's run docker container run simple-nodejs-app:latest and go to localhost:3000 to check if our app is running.
Did you figure out why the app is not showing on localhost:3000? It's actually running on port 3000 of the container but we still need to map that port to a port on our machine ??. Try docker container run -p 3000:3000 simple-nodejs-app:latest and you should be seeing a running application from a custom docker image ??