Docker Image Management
Tags: docker, linux, beginners
Published: 2023-03-06 04:57:41

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkoopxexhloipwbvq6d6.png)

---

## Docker Image Archive

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

- docker save/docker load




![Save/Load](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6w7ecghr2nresu35o0bo.png)





- docker export/docker import



![Export/Import](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqxz8rvh6050hbl0c2c4.png)





👉 `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

```bash
mkdir /image_backup
```
Checking current images detail

```bash
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
```

To save a backup for a specific image

```bash
docker save -o /image_backup/ubuntu.tar ubuntu:bionic
```

So now if we delete the image

```bash
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
```

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

```bash
docker load -i /image_backup/ubuntu.tar
```

Checking the results

```bash
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
```

### Export/Import

Creating a new container using the ubuntu image

```bash
docker run -d --name image_con ubuntu:bionic
```

Exporting this container

```bash
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
```

Now we can import this image with a different tag

```bash
docker import /image_backup/image_con.tar myubuntu:v1
```

Checking the results

```bash
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
```

---

## Docker Image Commit & Build

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


![Commit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0n1vbfh1888uerz2gffo.png)



`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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7i944a3k7unp3kepd98c.png)

`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

```bash
docker inspect ubuntu:bionic

"Layers": [
                "sha256:52c5ca3e9f3bf4c13613fb3269982734b189e1e09563b65b670fc8be0e223e03"
            ]
```

Creating the container using the `-it` option

```bash
docker run -it --name commit_con ubuntu:bionic
```

From inside the container,

```bash
cat > TestFile
TestFile

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

Now from the host machine

```bash
docker commit -a "MyName" -m "Image Comment" commit_con myubuntu:v1.0.0
```

We can confirm this commit using

```bash
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myubuntu            v1.0.0              2e214faf96f7        32 seconds ago      63.1MB
```

Inspecting our v1.0.0 ubuntu image

```bash
            "Layers": [
                "sha256:52c5ca3e9f3bf4c13613fb3269982734b189e1e09563b65b670fc8be0e223e03",
                "sha256:cea6ad35f448cdba9f2bb5c32b245c497e90cafa36c8f856706c8257bb666e34"
            ]
```

👉 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

```bash
mkdir /DockerFile_root
cd /DockerFile_root
```

Creating an empty dockerfile

```bash
vi Dockerfile # Using this default name is more efficient
```

Adding the following inside this file

```bash
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 
```

Creating an `index.html`

```bash
echo Test > index.html
```

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

This is where the docker build should happen

```bash
docker build -t my:v1.0.0 ./
Successfully built e5581d044d20
Successfully tagged my:v1.0.0
```


![Build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j3982hetel2t8nx63pqt.png)



Checking our new image

```bash
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
my                  v1.0.0              e5581d044d20        28 seconds ago      204MB
```

Creating and starting the container 

```bash
docker run -d --name apache2_con -p 80:80 my:v1.0.0
891473bcd83ed3f94830e5595aa698f3e40652183183a205bc3aa60fe8eef843
```

If we check inside the container

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

Returning to the host system and checking port 80

```bash
curl localhost:80
Test

curl localhost:80/Test.html
RunTest
```

---

## Docker Build Cache & Image Sizes

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

Creating a new Dockerfile

```bash
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
```

To build this image

```bash
docker build -t dummy:v1.0.0 ./ -f Dockerfile_L
```
👉 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

```bash
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
dummy               v1.0.0              4ef94c4001fe        2 minutes ago       168MB
```


![3 Run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aawwlki7lv4m193dfn8.png)



👉 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

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

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

```bash
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
```

![1 RUN](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqerzp3qk4tax0bespsg.png)

✨ 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

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

To compile our `C` code, we need the `gcc` compiler

```bash
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"]
```

Now building this Dockerfile

```bash
docker build -t multi:v1.0.0 ./ -f Dockerfile_M
```

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

If we check the image size 

```bash
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
```

Testing the image by running it in a container

```bash
docker run -it --rm --name pritn_c multi:v1.0.0
Hello World
```

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

```bash
# 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"]
```

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 

```bash
docker build -t multi:v1.0.1 ./ -f Dockerfile_M
``` 

Checking this image size

```bash
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
multi               v1.0.1              1cc2b8149d7d        6 seconds ago       7.23MB
```

Testing the image by running it in a container

```bash
docker run -it --rm --name print_c multi:v1.0.1
Hello World
```

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

```bash
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"]
```

Building the image

```bash
docker build -t multi:v1.0.2 ./ -f Dockerfile_M2
```

Checking the size

```bash
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
multi               v1.0.2              60500955b71d        4 seconds ago       7.23MB
```

Testing the image by running it in a container

```bash
docker run -it --rm --name print_c multi:v1.0.2
Hello World
```

> 💡 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 ✔