The magical world of containers — a new Docker image

Gabriele de Capoa
5 min readAug 17, 2022

--

Photo by Guillaume Bolduc on Unsplash

Anyone in my LinkedIn network knows in 2020 I became a Certified Kubernetes Application Developer. After about 4 years working actively on Kubernetes day by day, this certification was a great recognition of my studies.
This is why I decided to start a new post series, describing what I’ve learned about containers and their use cases. Topics will be:

* Basic concepts
* Use cases
* Unix background
* Namespaces
* Control Groups
* chroot
* Implementations
* Docker
* containerd
* Docker commands
* Orchestration and Docker Swarm
* Kubernetes
* Architecture
* Objects
* CLI
* Cloud Foundry
* Architecture
* Application development
* CLI
* OpenShift
* Architecture
* Objects
* CLI

Before starting, I would add a caveat: this is what I understood after studying on books and on the job, but could include lots of misunderstanding, so please use those posts just a starting point to deepen your knowledge, starting your own learning roadmap (and eventually point me to the misunderstandings)!

After we described how to download and use a pre-built Docker image, now we’ll see how to create a custom Docker image and share with other people.

To create a Docker image where you could run your application, you have to create a Dockerfile like this:

FROM node:dubnium-jessie-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ “node”, “server.js” ]

A Dockerfile is a special script file, which provides to Docker engine instructions about how to create a new image. Keyword that could be used, written in capital letters, are:

  • FROM Used as FROM <image>[:<tag>|@<digest>][AS <name>] Initialises a new build stage and sets the Base Image for subsequent instructions. It might occurs multiple times (to create multiple images or use one build stage as a dependency for another) and it might be preceded by ARG.
  • RUN Used as RUN <command> or RUN ["executable", “param1", “param2”, ...] Will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step.
  • CMD It might be unique in Dockerfile, and its main purpose is to provide defaults for an executing container.
  • LABEL Used as LABEL <key>=<value> ..., adds metadata to an image
  • EXPOSE Used as EXPOSE <port> [<port>/<protocol> ...], informs Docker that the container listens on the specified network ports at runtime, without publishing the port. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified.
  • ENV Used as ENV <key>=<value> ... Sets the environment variable <key> to the value <value>. The environment variables set using ENVwill persist when a container is run from the resulting image.
  • ADD Used as ADD [--chown=<user>:<group>]["<src>",... "<dest>"] Copies new files, directories or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest> (with chown supported only on Linux containers).
  • COPY Used as COPY [--chown=<user>:<group>]["<src>",... "<dest>"], is similar to ADD.
  • ENTRYPOINT Used as ENTRYPOINT ["executable","param1","param2",...], allows you to configure a container that will run as an executable.
  • VOLUME Used as VOLUME ["/data"], creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.
  • USER Used as USER <user>[:<group>], sets the user name (or UID) and optionally the user group (or GID) to use when running the image and for any RUN, CMD and ENTRYPOINT instructions that follow it.
  • WORKDIR Used as WORKDIR </path/to/workdir>, sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it.
  • ARG Used as ARG <name>[=<default_value>], defines a variable that users can pass at build-time to the builder with the specific Docker command using a specific flag. If a user specifies a build argument that was not defined in the Dockerfile, the build outputs a warning.

Once you defined a Dockerfile , to build actually your custom image, you need to run the following command:

docker build -t <image_name> .

Dot character at the end of command represent the location of Dockerfile ( . means “here”).

How Docker images get built

As we said in previous post of this series, Docker containers are building blocks for applications.

The build process starts from a build context, that is a set of local files and directories provided to docker build command as parameter (i.e. the dot); each instruction in Dockerfile define a new image layer, created starting from previous layer (so the previous instruction). These layers (also called intermediate images) are generated when the commands in the Dockerfile are executed during the Docker image build. Each container is an image with a readable/writeable layer on top of a bunch of read-only layers.

When Docker builds the container from the above Dockerfile, each step corresponds to a command run in Dockerfile, and each layer is made up of the file generated from running that command. Along with each step, the layer created is listed represented by its random generated ID.

Layer creation leverages ephimeral containers for building phase, as build must be always inside a container. Moreover, in order to speed up the build phase, a cache is used.

Image from Swetava’s Blog

Saying that, we could see that a container is an image with a readable/writeable layer on top of a bunch of read-only layers. Let’s see an example of image building.

$ docker build -t expressweb .Step 1 : FROM node:argon
argon: Pulling from library/node...
...
Status: Downloaded newer image for node:argon
— -> 530c750a346e
Step 2 : RUN mkdir -p /usr/src/app
— -> Running in 5090fde23e44
— -> 7184cc184ef8
Removing intermediate container 5090fde23e44
Step 3 : WORKDIR /usr/src/app
— -> Running in 2987746b5fba
— -> 86c81d89b023
Removing intermediate container 2987746b5fba
Step 4 : COPY package.json /usr/src/app/
— -> 334d93a151ee
Removing intermediate container a678c817e467
Step 5 : RUN npm install
— -> Running in 31ee9721cccb
— -> ecf7275feff3
Removing intermediate container 31ee9721cccb
Step 6 : COPY . /usr/src/app
— -> 995a21532fce
Removing intermediate container a3b7591bf46d
Step 7 : EXPOSE 8080
— -> Running in fddb8afb98d7
— -> e9539311a23e
Removing intermediate container fddb8afb98d7
Step 8 : CMD npm start
— -> Running in a262fd016da6
— -> fdd93d9c2c60
Removing intermediate container a262fd016da6
Successfully built fdd93d9c2c60

To view layers of an image you could use this command:

docker history <image_name> 

All layers built on a machine (physical or virtual) are saved in /var/lib/docker/aufs directory. In this directory exists symbolic links to three other folders, diff (layers and theirs contents), layers (how images are stacked) and mnt (running containers). Digging into those folders could be useful to view the contents of each layer.

Once you build a new Docker image and you will share this inside your organization or make it public, you need to login into an image registry and then push the image.

docker login
docker push <image\_name>

--

--

Gabriele de Capoa

Cloud software engineer, wanna-be data scientist, former Scrum Master. Agile, DevOps, Kubernetes and SQL are my top topics.