The magical world of containers — a new Docker image
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 asFROM <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 byARG
.RUN
Used asRUN <command>
orRUN ["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 inDockerfile
, and its main purpose is to provide defaults for an executing container.LABEL
Used asLABEL <key>=<value> ...
, adds metadata to an imageEXPOSE
Used asEXPOSE <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 asENV <key>=<value> ...
Sets the environment variable<key>
to the value<value>
. The environment variables set usingENV
will persist when a container is run from the resulting image.ADD
Used asADD [--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>
(withchown
supported only on Linux containers).COPY
Used asCOPY [--chown=<user>:<group>]["<src>",... "<dest>"]
, is similar toADD
.ENTRYPOINT
Used asENTRYPOINT ["executable","param1","param2",...]
, allows you to configure a container that will run as an executable.VOLUME
Used asVOLUME ["/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 asUSER <user>[:<group>]
, sets the user name (or UID) and optionally the user group (or GID) to use when running the image and for anyRUN
,CMD
andENTRYPOINT
instructions that follow it.WORKDIR
Used asWORKDIR </path/to/workdir>
, sets the working directory for anyRUN
,CMD
,ENTRYPOINT
,COPY
andADD
instructions that follow it.ARG
Used asARG <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 theDockerfile
, 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.
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>