DEV Community

Cover image for Docker Image Management
Waji
Waji

Posted on

Docker Image Management

Introduction

Docker image management involves creating, managing, and distributing Docker images. Docker images are the building blocks of Docker containers, which are lightweight and portable virtualized environments that can run anywhere. It uses a layered architecture to build and manage Docker images. Each layer in the Docker image represents a specific set of changes or additions to the previous layer.

Docker Image


Docker Image Archive

Docker provides two different methods to save and load Docker images:

  • docker save/docker load

Save/Load

  • docker export/docker import

Export/Import

👉 docker save and docker load are used to save and load entire Docker images along with all of their layers and metadata.

👉 docker export and docker import are used to export and import a container as a tar file. Docker export and docker import do not include metadata or information about the image's layers.


Short hands on

Save/Load

Creating an empty directory

mkdir /image_backup
Enter fullscreen mode Exit fullscreen mode

Checking current images detail

docker images;
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              904b8cb13b93        4 days ago          142MB
ubuntu              bionic              b89fba62bc15        4 days ago          63.1MB
mysql               latest              4f06b49211c0        10 days ago         530MB
mysql               5.7                 be16cf2d832a        4 weeks ago         455MB
Enter fullscreen mode Exit fullscreen mode

To save a backup for a specific image

docker save -o /image_backup/ubuntu.tar ubuntu:bionic
Enter fullscreen mode Exit fullscreen mode

So now if we delete the image

docker rmi ubuntu:bionic

# Checking for the image
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              904b8cb13b93        4 days ago          142MB
mysql               latest              4f06b49211c0        10 days ago         530MB
mysql               5.7                 be16cf2d832a        4 weeks ago         455MB
Enter fullscreen mode Exit fullscreen mode

We can load the ubuntu:bionic image from the backup directory

docker load -i /image_backup/ubuntu.tar
Enter fullscreen mode Exit fullscreen mode

Checking the results

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              904b8cb13b93        4 days ago          142MB
ubuntu              bionic              b89fba62bc15        4 days ago          63.1MB
mysql               latest              4f06b49211c0        10 days ago         530MB
mysql               5.7                 be16cf2d832a        4 weeks ago         455MB
Enter fullscreen mode Exit fullscreen mode

Export/Import

Creating a new container using the ubuntu image

docker run -d --name image_con ubuntu:bionic
Enter fullscreen mode Exit fullscreen mode

Exporting this container

docker export image_con -o /image_backup/image_con.tar

ls -l /image_backup/
total 127984
-rw------- 1 root root 65521664 Mar  6 09:34 image_con.tar
-rw------- 1 root root 65529856 Mar  6 09:30 ubuntu.tar
Enter fullscreen mode Exit fullscreen mode

Now we can import this image with a different tag

docker import /image_backup/image_con.tar myubuntu:v1
Enter fullscreen mode Exit fullscreen mode

Checking the results

docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myubuntu            v1                  3e3a5217f941        3 seconds ago       63.1MB
nginx               latest              904b8cb13b93        4 days ago          142MB
ubuntu              bionic              b89fba62bc15        4 days ago          63.1MB
Enter fullscreen mode Exit fullscreen mode

Docker Image Commit & Build

docker commit and docker build are both Docker commands used to create Docker images

Commit

docker commit command is used to create a new Docker image from an existing container. This command is useful when you have made changes to a running container, and you want to save those changes as a new image.

Build

docker build command is used to create a new Docker image from a Dockerfile. A Dockerfile is a script that contains instructions on how to build a Docker image

Some of the commonly used Dockerfile instructions:

  • FROM: This instruction is used to specify the base image that the new image will be built on top of
  • LABEL: This instruction is used to add metadata to the image
  • RUN: This instruction is used to execute commands within the container
  • ADD: This instruction is used to copy files from the host system into the container
  • WORKDIR: This instruction is used to set the working directory for any subsequent commands in the Dockerfile
  • EXPOSE: This instruction is used to specify which port(s) the container will listen on at runtime
  • CMD: This instruction is used to specify the default command to be executed when the container starts
  • ENTRYPOINT: This instruction is used to specify the command that will be executed when the container starts, and it cannot be overridden when the container is run

Short hands on

Docker Commit

To check how many layers are there in the ubuntu image

docker inspect ubuntu:bionic

"Layers": [
                "sha256:52c5ca3e9f3bf4c13613fb3269982734b189e1e09563b65b670fc8be0e223e03"
            ]
Enter fullscreen mode Exit fullscreen mode

Creating the container using the -it option

docker run -it --name commit_con ubuntu:bionic
Enter fullscreen mode Exit fullscreen mode

From inside the container,

cat > TestFile
TestFile

ls
TestFile  boot  etc   lib    media  opt   root  sbin  sys  usr
bin       dev   home  lib64  mnt    proc  run   srv   tmp  var
Enter fullscreen mode Exit fullscreen mode

Now from the host machine

docker commit -a "MyName" -m "Image Comment" commit_con myubuntu:v1.0.0
Enter fullscreen mode Exit fullscreen mode

We can confirm this commit using

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myubuntu            v1.0.0              2e214faf96f7        32 seconds ago      63.1MB
Enter fullscreen mode Exit fullscreen mode

Inspecting our v1.0.0 ubuntu image

            "Layers": [
                "sha256:52c5ca3e9f3bf4c13613fb3269982734b189e1e09563b65b670fc8be0e223e03",
                "sha256:cea6ad35f448cdba9f2bb5c32b245c497e90cafa36c8f856706c8257bb666e34"
            ]
Enter fullscreen mode Exit fullscreen mode

👉 We can see that a new image was created with an extra layer when we committed the changes for our ubuntu container

Docker Build

Creating an empty directory for our dockerfile

mkdir /DockerFile_root
cd /DockerFile_root
Enter fullscreen mode Exit fullscreen mode

Creating an empty dockerfile

vi Dockerfile # Using this default name is more efficient
Enter fullscreen mode Exit fullscreen mode

Adding the following inside this file

FROM ubuntu:bionic  
LABEL maintainer "Author <Author@localhost.com>" # (Key : Value) Format
RUN apt-get update && apt-get install apache2 -y 
ADD index.html /var/www/html
WORKDIR /var/www/html # works just like 'cd' 
RUN ["/bin/bash", "-c", "echo RunTest > Test.html"]
EXPOSE 80
CMD ["apachectl", "-DFOREGROUND"] # Either have to use 'CMD' or 'ENTRYPOINT' to run the service 
Enter fullscreen mode Exit fullscreen mode

Creating an index.html

echo Test > index.html
Enter fullscreen mode Exit fullscreen mode

Our working directory is currently /DockerFile_root which is also known as Build Context Directory

This is where the docker build should happen

docker build -t my:v1.0.0 ./
Successfully built e5581d044d20
Successfully tagged my:v1.0.0
Enter fullscreen mode Exit fullscreen mode

Build

Checking our new image

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
my                  v1.0.0              e5581d044d20        28 seconds ago      204MB
Enter fullscreen mode Exit fullscreen mode

Creating and starting the container

docker run -d --name apache2_con -p 80:80 my:v1.0.0
891473bcd83ed3f94830e5595aa698f3e40652183183a205bc3aa60fe8eef843
Enter fullscreen mode Exit fullscreen mode

If we check inside the container

docker exec -it apache2_con /bin/bash
root@891473bcd83e:/var/www/html# cat ./Test.html 
RunTest
Enter fullscreen mode Exit fullscreen mode

Returning to the host system and checking port 80

curl localhost:80
Test

curl localhost:80/Test.html
RunTest
Enter fullscreen mode Exit fullscreen mode

Docker Build Cache & Image Sizes

Finally, I want to discuss regarding Docker build sizes and how to manage them

Creating a new Dockerfile

vi Dockerfile_L

FROM ubuntu:bionic
LABEL maintainer "Author <Author@localhost.com>"
RUN mkdir /dummy
RUN fallocate -l 100m /dummy/A
RUN rm -rf /dummy/A
Enter fullscreen mode Exit fullscreen mode

To build this image

docker build -t dummy:v1.0.0 ./ -f Dockerfile_L
Enter fullscreen mode Exit fullscreen mode

👉 The -f option in the docker build command specifies the Dockerfile name to use during the build process, when the Dockerfile is not named "Dockerfile"

This will build the image as follows

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
dummy               v1.0.0              4ef94c4001fe        2 minutes ago       168MB
Enter fullscreen mode Exit fullscreen mode

3 Run

👉 As we can see that it is 168MBs big. We know that ubuntu:bionic is only about 63MBs. In the Dockerfile we just created dummy directory and deleted a file inside the directory that cost us over 100MBs

To decrease the size we can try to edit our Dockerfile

FROM ubuntu:bionic
LABEL maintainer "Author <Author@localhost.com>"
RUN mkdir /dummy && \
fallocate -l 100m /dummy/A && \
rm -rf /dummy/A
Enter fullscreen mode Exit fullscreen mode

Now if we check the image size after building this new image

docker build -t dummy:v1.0.1 ./ -f Dockerfile_L

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
dummy               v1.0.1              6ced40e23a77        11 seconds ago      63.1MB
dummy               v1.0.0              4ef94c4001fe        8 minutes ago       168MB
Enter fullscreen mode Exit fullscreen mode

1 RUN

✨ Another important thing is Docker Cache

💡 Docker caches build layers to speed up subsequent builds of a Dockerfile. However, when using GitHub for building, changes to source code may not be properly reflected due to cached layers. To force a rebuild of all layers and ensure changes are properly incorporated, use the --no-cache option with the docker build command

We can try to compile a C code as well

vi ./app.c
#include <stdio.h>
void main()
{
 printf("Hello World\n");
}
Enter fullscreen mode Exit fullscreen mode

To compile our C code, we need the gcc compiler

vi Dockerfile_M

FROM gcc:latest
LABEL maintainer "Author <Author@localhost.com>"
ADD app.c /root
WORKDIR /root
RUN gcc -o ./app ./app.c
CMD ["./app"]
Enter fullscreen mode Exit fullscreen mode

Now building this Dockerfile

docker build -t multi:v1.0.0 ./ -f Dockerfile_M
Enter fullscreen mode Exit fullscreen mode

👉 This will first pull the gcc:latest image from the docker hub website

If we check the image size

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
multi               v1.0.0              8b00f89eb22c        23 seconds ago      1.27GB
gcc                 latest              c6aa7ca27d67        4 days ago          1.27GB
Enter fullscreen mode Exit fullscreen mode

Testing the image by running it in a container

docker run -it --rm --name pritn_c multi:v1.0.0
Hello World
Enter fullscreen mode Exit fullscreen mode

Well this works but the size of images are obnoxiously large for such a simple task 😥

To solve this, we can edit the Dockerfile as follows

# GCC Compile Block
FROM gcc:latest as compile_base
LABEL maintainer "Author <Author@localhost.com>"
ADD app.c /root
WORKDIR /root
RUN gcc –o ./app ./app.c

# APP Running Block
FROM alpine:latest
RUN apk add --no-cache gcompat
WORKDIR /root
COPY --from=compile_base /root/app ./ # we set an alias in the compile block as "compile_base" which is being used here
CMD ["./app"]
Enter fullscreen mode Exit fullscreen mode

This is called multi-stage build. Multi-stage builds in Docker allow you to use multiple FROM statements in a single Dockerfile to create multiple intermediate images, each with its own set of instructions and layers.

✨ The advantage of using multi-stage builds is that it allows you to create smaller and more efficient Docker images.

Now we will build this image

docker build -t multi:v1.0.1 ./ -f Dockerfile_M
Enter fullscreen mode Exit fullscreen mode

Checking this image size

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
multi               v1.0.1              1cc2b8149d7d        6 seconds ago       7.23MB
Enter fullscreen mode Exit fullscreen mode

Testing the image by running it in a container

docker run -it --rm --name print_c multi:v1.0.1
Hello World
Enter fullscreen mode Exit fullscreen mode

We can even reduce this image size further by dividing the Package install block as well

vi Dockerfile_M2

# GCC Compile Block
FROM gcc:latest as compile_base
LABEL maintainer "Author <Author@localhost.com>"
ADD app.c /root
WORKDIR /root
RUN gcc –o ./app ./app.c

# Package Install Block
FROM alpine:latest as package_install
RUN apk add --no-cache gcompat
WORKDIR /root
COPY --from=compile_base /root/app ./

# App running Block
FROM package_install as run
WORKDIR /root
CMD ["./app"]
Enter fullscreen mode Exit fullscreen mode

Building the image

docker build -t multi:v1.0.2 ./ -f Dockerfile_M2
Enter fullscreen mode Exit fullscreen mode

Checking the size

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
multi               v1.0.2              60500955b71d        4 seconds ago       7.23MB
Enter fullscreen mode Exit fullscreen mode

Testing the image by running it in a container

docker run -it --rm --name print_c multi:v1.0.2
Hello World
Enter fullscreen mode Exit fullscreen mode

💡 The size different can't be seen in the above case as the actual compile file is really small as compared to an actual program


Conclusion

In conclusion, managing Docker images is an important part of working with Docker containers. By understanding the various Docker image management commands and best practices I discussed above, users can effectively manage their Docker images to optimize their container environment and workflow ✔

Top comments (7)

Collapse
 
kadz93 profile image
Markus

Super nice article great work!

Collapse
 
waji97 profile image
Waji

Thank you Markus :)

Collapse
 
bhanukiranchaluvadi profile image
BhanuKiranChaluvadi

Hi,

Great article. I still do not understand why moving from a 2 stage build to a 3 stage build saves memory. Could explain it a bit more ?

Collapse
 
leesei profile image
leesei • Edited

The first stage builds the app.
The second stage installs the app's dependency.
The third stage copies only the app and dependencies from the second stage.

The saving will be the package manager itself. Say we need to install Node's Yarn or Python's poetry.
The second saving will be the package Manager's cache and temp files. We used to disable caching when using two stage builds.

Collapse
 
waji97 profile image
Waji

Hey,

In my example above, I divided the App running block into 2 parts which should technically save more memory. However, as I was testing a simple C language code that gives "Hello World" as an output, the memory decrement wasn't visible.

Normally, multi-staging our Dockerfiles should reduce the size of Docker images leading to faster deployment times.

Collapse
 
cduran profile image
Carlos Duran

Awesome article, great usage of images to visually explain the Docker concepts. Thanks for sharing!

Collapse
 
waji97 profile image
Waji

Thank you!