Docker images and beyond

Learn how to docker

Omkar Birade
Jan 21 · 10 min read

What are images?

Images can be thought of as a template from which containers are built.

Think of images as a cast and containers as entities that are made from it.

You can form as many containers you want from an image.

Images make it very easy to create containers.

All containers of the same image will be identical to each other.

All the dependencies and resources are packed in the image. Even the operating system is a part of the image.

Multiple containers can be instantiated from the same image

Above we have a cast/image called nginx. This image lets us build as many containers as we need.

To start with millions of images are readily available on docker hub which covers the most common use cases.


Image registry and Docker Hub

Docker hub is the place where all docker images are stored. You can check it out here and make an account if you don’t have one already.

It is the official image registry of docker.

You can pull/download and push/upload images from docker hub.

Let us try pulling an apache image from docker hub. It’s name is httpd on docker hub.

docker pull httpd//Command returns
Using default tag: latest
latest: Pulling from library/httpd
8ec398bc0356: Pull complete
354e6904d655: Pull complete
27298e4c749a: Pull complete
10e27104ba69: Pull complete
36412f6b2f6e: Pull complete
Digest: sha256:769018135ba22d3a7a2b91cb89b8de711562cdf51ad6621b2b9b13e95f3798de
Status: Downloaded newer image for httpd:latest
docker.io/library/httpd:latest

We have successfully pulled httpd image from docker hub.

Let’s create and run a container from this image using the following command

docker container run -d -p 45:80 httpd

Navigate to http://localhost:45 and you will see default page of apache saying “It works”.

You can also verify that the container is running through docker container ls command which returns all running containers.

Let us explore how these casts/images are built.


How are images created?

Let us dig deep into understanding images and go about exploring on how images are created.

Images are created using something called as a Dockerfile.

A Dockerfile is a text document that contains all the commands/instructions you could call on the command line to create an image.

Let’s create a simple nginx image that servers our own page.

We will understands the specifics in the next section.

Right now, let’s create a new image called mynginx image follwing the steps below:

  1. Copy this html code in a file called index.html file anywhere on your machine.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome Keanu</h1>
</body>
</html>

2. At the same place copy the docker file and name the file as dockerfile with no extensions, it will be a text file.

FROM nginx:latestWORKDIR /usr/share/nginx/htmlCOPY . .

3. Run the following command

docker build -t mynginx:latest .//Firing the command gives
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM nginx:latest
latest: Pulling from library/nginx
8ec398bc0356: Pull complete
dfb2a46f8c2c: Pull complete
b65031b6a2a5: Pull complete
Digest: sha256:8aa7f6a9585d908a63e5e418dc5d14ae7467d2e36e1ab4f0d8f9d059a3d071ce
Status: Downloaded newer image for nginx:latest
---> c7460dfcab50
Step 2/3 : WORKDIR /usr/share/nginx/html
---> Running in a58173b4cebd
Removing intermediate container a58173b4cebd
---> 518527034854
Step 3/3 : COPY . .
---> 8175154ea5f2
Successfully built 8175154ea5f2
Successfully tagged mynginx:latest

Don’t forget the dot at the end.

Note: Use -f flag to specify location of the file when anywhere other than the current directory.

4. To view your newly created image run

docker image ls //returns all images

You will see an image like this

REPOSITORY    TAG      IMAGE ID        CREATED          SIZE
mynginx latest afd91fd17d4b 21 hours ago 126MB

5. Create a container and verify nginx server our custom html page

docker container run -d -p 8080:80 mynginx //returns image id
3b8098c31c8ce4e0fce599214075ffbf920dfebe701a99315583c464ded09140

6. Navigate to http://localhost:8080

7. You will see the served page with header “Welcome Keanu”.

8. We have successfully created our own image and deployed an independent container of the image:)


Breaking down the Dockerfile and understanding it line by line

Let us check the Dockerfile again

FROM nginx:latestWORKDIR /usr/share/nginx/htmlCOPY . .

It has 3 commands:

  1. FROM nginx:latest

We have already discussed docker even packs the OS with the image.

FROM directive helps us specify which OS to use as base image, you can also choose an existing image as the base image as in our case we used nginx as our base image.

Here, latest is just a tag

A tag is an alias for the version of the image. Using tags you can specify which version of the image to pull.

Like in our case latest tag points to the latest version of the image.

If you don’t specify a tag with the image name, by default latest tag is used.

2. WORKDIR

WORKDIR can do both cd and md . It allows you to change directory while Docker is building our custom image or makes a new one if that directory doesn’t exist already. The changed directory becomes the current directory for executing subsequent build instructions that follow it.

As in our case we want to copy our html file to our containers /usr/share/nginx/html directory.

3. COPY

COPY directive is self explanatory. It copies the files from source to destination.

COPY source-path destination-path

In our case we are copying from the existing image hence the dot to the current directory of our container hence followed by another dot


Digging deep down into images

Images have a layered structure. Every command in Dockerfile creates a layer in our image.

Let us again build this image and see the layers forming in action.

  1. Delete the existing image we created
docker rmi mynginx
OR
docker rmi <image-id>

Note: Remove all containers running from this image first and then delete them image

docker container rm <container-id>

2. Build a fresh mynginx image

docker build -t mynginx:latest .//Output
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM nginx:latest
latest: Pulling from library/nginx
8ec398bc0356: Pull complete
dfb2a46f8c2c: Pull complete
b65031b6a2a5: Pull complete
Digest: sha256:8aa7f6a9585d908a63e5e418dc5d14ae7467d2e36e1ab4f0d8f9d059a3d071ce
Status: Downloaded newer image for nginx:latest
---> c7460dfcab50
Step 2/3 : WORKDIR /usr/share/nginx/html
---> Running in f8adb5dc5a47
Removing intermediate container f8adb5dc5a47
---> a59fb35a43c2
Step 3/3 : COPY . .
---> 7911d9a451d9
Successfully built 7911d9a451d9
Successfully tagged mynginx:latest

As you can see every step gives a new image with a different image id forming our custom image layer by layer.

Step 1 use nginx image pulled from docker hub with id c7460dfcab50

Step 2 creates a container with id f8adb5dc5a47 and runs the command in it and stacks another layer on the image with id a59fb35a43c2

Note: You will notice “removing intermediate container”. This is because every command is executed in an intermediate container. Any changes to file system are added as a layer to the image. The intermediate container can be removed after execution is finished after every step.

Step 3 creates out final image by stacking layer with id 7911d9a451d9 on it.

The final image can be viewed by running docker image ls command which produces this output

REPOSITORY     TAG        IMAGE ID       CREATED          SIZE
mynginx latest 7911d9a451d9 3 minutes ago 126MB
nginx latest c7460dfcab50 7 days ago 126MB

You can also view each layer of image and additional details using docker history with command.

docker history mynginx//Output
IMAGE CREATED CREATED BY SIZE COMMENT
c409ab41a017 4 seconds ago /bin/sh -c #(nop) COPY dir:adb8e02539799beac… 206B
a5c7c5cee63b 4 seconds ago /bin/sh -c #(nop) WORKDIR /usr/share/nginx/h… 0B
c7460dfcab50 10 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 10 days ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 10 days ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 10 days ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx… 22B
<missing> 10 days ago /bin/sh -c set -x && addgroup --system -… 57.1MB
<missing> 10 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.3.7 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.17.7 0B
<missing> 3 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:04caaf303199c81ff… 69.2MB

docker inspect

docker inspect let’s you see all the data about about docker objects like images, containers etc.

docker inspect mynginx//Output
[
{
"Id": "sha256:c409ab41a017a309d0a639b972cb1f0577a328789d4fb9aa3865ef2a0e719955",
"RepoTags": [
"mynginx:latest"
],
"RepoDigests": [],
"Parent": "sha256:a5c7c5cee63b1982a5f3c9c7cc5a07ead61a0f5aa0e9ae884f467f69376b5037",
"Comment": "",
"Created": "2020-01-20T07:29:58.791831672Z",
"Container": "",
"ContainerConfig": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [

...
//Output truncated as it is too large

Layer reuse

Let us try building the same image again.

docker build -t mynginx:latest .//Output
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM nginx:latest
---> c7460dfcab50
Step 2/3 : WORKDIR /usr/share/nginx/html
---> Using cache
---> a5c7c5cee63b
Step 3/3 : COPY . .
---> Using cache
---> c409ab41a017
Successfully built c409ab41a017
Successfully tagged mynginx:latest

You will notice that the process was significantly faster this time.

Can you guess why?

Checking out the output we can see “using cache” multiple times. What is happening here is docker is using already built layers.

This is called layer reuse.

Docker sees that this image was already built and Dockerfile has no changes whatsoever. So it uses cached layers.

Let us see what happens when you change the Dockerfile.

  1. Create a new folder in the folder where our Dockerfile is, and name it as newhtml.
  2. Create a file called index.html and copy the following code in that file.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Oh! So you came again.</h1>
</body>
</html>

3. Change the COPY . . command to COPY ./newhtml .

4. Build this Dockerfile again.

docker build -t mynginx:latest .//Output
Sending build context to Docker daemon 4.608kB
Step 1/3 : FROM nginx:latest
---> c7460dfcab50
Step 2/3 : WORKDIR /usr/share/nginx/html
---> Using cache
---> a5c7c5cee63b
Step 3/3 : COPY ./newhtml .
---> ca3006e5bd33
Successfully built ca3006e5bd33
Successfully tagged mynginx:latest

So what just happened here?

We see only one layer is being used from cache. The subsequent layer is build again as it is different.

docker compares the layers on by one and reuses it. Wherever it seems a difference it builds again.

Note: As soon as a layer has changed, all subsequent layers are rebuilt, even if they are exactly the same.

Hence, it is recommended to try to keep all non-changing layers at the top of the Dockerfile so that cached layers can be used.

Interesting Note: You can check the layers formed by docker and the diffs here /var/lib/docker/overlay2 .


Making your image available from anywhere

Now that you have successfully created your own image. It’s time to put it online:)

Docker hub is place where you can host all your images

Let’s push our image to docker hub.

  1. Run docker login command and authenticate yourself.
  2. Tag your image with your username so that docker can associate it with your account.
docker tag mynginx <dockerhub-username>/mynginx

3. Push the image

docker push <dockerhub-username>/mynginx//Output
The push refers to repository [docker.io/omkar20/mynginx]
7e5092bfebcc: Pushed
c26e88311e71: Pushed
17fde96446df: Pushed
556c5fb0d91b: Pushed
latest: digest: sha256:34fb746d35b2340d87ad0e3bfad0926b58374c26302129cd5a975259983a4f1f size: 1155

You successfully pushed your image to your repository:)

Now you can pull it from anywhere. Let’s try it out

docker rmi omkar20/mynginx // remove existing image
docker login
docker pull omkar20/mynginx
//Output
Using default tag: latest
latest: Pulling from omkar20/mynginx
8ec398bc0356: Already exists
dfb2a46f8c2c: Already exists
b65031b6a2a5: Already exists
ae6a7fb36eed: Pull complete
Digest: sha256:34fb746d35b2340d87ad0e3bfad0926b58374c26302129cd5a975259983a4f1f
Status: Downloaded newer image for omkar20/mynginx:latest
docker.io/omkar20/mynginx:latest
docker image ls//Output
REPOSITORY TAG IMAGE ID CREATED SIZE
omkar20/mynginx latest ca3006e5bd33 3 hours ago 126MB
nginx latest c7460dfcab50 10 days ago 126MB

You successfully pulled mynginx image from your docker hub repository

Container from images

All layers of image are read only.

So what happens to the changes that container makes and where do containers write their data?

Let’s look into their structure

Whenever you create a new container, it creates a thin writable layer over the image.

Because each container has its own writable container layer, and all changes are stored in this container layer, multiple containers can share access to the same underlying image and yet have their own data state. The diagram below shows multiple containers sharing the above Ubuntu 15.04 image.

Now that you have made it this far, you have an in-depth idea about what docker images are.

Happy Learning:)

Follow us on Twitter 🐦 and Facebook 👥 and Instagram 📷 and join our Facebook and Linkedin Groups 💬.

To join our community Slack team chat 🗣️ read our weekly Faun topics 🗞️, and connect with the community 📣 click here⬇

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author! ⬇

Faun

The Must-Read Publication for Aspiring Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

Omkar Birade

Written by

Co-Founder at Interleap who likes to solve trivial to complex problems in most creative ways.

Faun

Faun

The Must-Read Publication for Aspiring Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

More From Medium

More from Faun

More from Faun

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade