How to create your own local Docker Images with Dockerfile

By | June 3, 2023

In another article, we learned the basic docker commands like how to launch a container and connect to it via terminal and run commands. We saw that a basic docker image like "ubuntu" does not have commands like ifconfig, ip, and ping installed by default and we could install them with the apt command.

But here is the problem, if you stop the container and restart it, all modifications made to the container while it was running, would be lost and you would have to run a lot of the commands again.

To solve this problem we have the "Dockerfile" which allows us to create a customised image with a lot of commands already executed inside it and things pre-installed.

Its actually super-easy once you learn it.

1. Create a simple directory

Create a directory, so that we can create the Dockerfile inside and and play with it.

mkdir docker
cd docker

2. Now create your Dockerfile

Now inside the same directory create the Dockerfile and fill it with some instructions. Note that the file can be named anything, however over here we are naming it "Dockerfile" explicitly.

nano Dockerfile

Now our objective is to have a ubuntu based container along with some packages pre-installed. For this we shall use the following instructions in the Dockerfile.

FROM ubuntu
RUN apt-get update && apt-get install -y iputils-ping net-tools iproute2
CMD bash

3. Build the Image

Now we are ready to build our first docker image using the Dockerfile created in the previous step. Make sure that you are in the same directory as above and run the following command

sudo docker build --tag 'my_docker_image' .

The --tag indicates the unique name of your docker image, it can be anything. In this example we name it "my_docker_image". You shall note that docker will actually run the apt-get commands to execute the whole installation process in order to build the image file.

This makes sense, because when you launch a container using this image, the packages will be present already for use.

Dockerfile name: By default the build operationg will look for a file named Dockerfile in the directory "." which is the current working directory. If you want to name your Dockerfile differently then use the following command:

sudo docker build --tag 'testing_image' -f my_docker_file .

In the above example my_docker_file is the name of the Dockerfile containing the instructions.

The build process will proceed the same way, if the packages are already available it will use them from cache.

$ sudo docker build --tag 'testing_image' -f my_docker_file .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu
 ---> 3b418d7b466a
Step 2/3 : RUN apt-get update && apt-get install -y iputils-ping net-tools iproute2
 ---> Using cache
 ---> 9488c5052d90
Step 3/3 : CMD bash
 ---> Using cache
 ---> 9f15c6f57515
Successfully built 9f15c6f57515
Successfully tagged testing_image:latest

4. Launch container using your image

Launch the container with the usual run operation.

$ sudo docker run -it my_docker_image

Now run the commands like ip, ifconfig and ping and you shall see they are pre-installed.

Check from the host machine that your docker image based container is running

$ sudo docker ps
CONTAINER ID   IMAGE             COMMAND             CREATED          STATUS          PORTS     NAMES
84f6611d4cd0   my_docker_image   "/bin/sh -c bash"   47 seconds ago   Up 46 seconds             hopeful_wescoff

Another way

Here is yet another command example to prepare a docker file and build an image using it. In this method you can name your docker file whatever you want.

Lets say our Dockerfile is named as my_docker_file. Now we can build the image using the following command

cat my_docker_file | sudo docker build -t my_docker_image_2 -

Note there is a dash at the end.

Since we are piping the contents of the docker file, in this method the contents of a docker file can come from a dynamic source instead of being read from a static file. Like generated from some other command or downloaded from a remote url. This makes the process of creating an image even more agile.

We can check the registered images on our docker system as follows:

$ sudo docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
my_docker_image_2   latest    9f15c6f57515   36 seconds ago   123MB
my_docker_image     latest    6d43f2031d1a   14 minutes ago   119MB
nginx               latest    f9c14fe76d50   4 days ago       143MB
hello-world         latest    9c7a54a9a43c   3 weeks ago      13.3kB
ubuntu              latest    3b418d7b466a   4 weeks ago      77.8MB

Note the my_docker_image and my_docker_image2 images we just create a while ago. Our images are created out of the ubuntu base image, however they are bigger in size compared to the ubuntu image. This is because we installed packages in it which make it bigger (and better!)

Lets run our 2nd docker image just like before and try running the ifconfig, ip, ping commands.

$ sudo docker run -it my_docker_image_2
root@8d0773ec7ade:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet  netmask  broadcast
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 15  bytes 1963 (1.9 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet  netmask
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

root@8d0773ec7ade:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
       valid_lft forever preferred_lft forever
35: eth0@if36: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet brd scope global eth0
       valid_lft forever preferred_lft forever
root@8d0773ec7ade:/# ping
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=58 time=46.6 ms
64 bytes from ( icmp_seq=2 ttl=58 time=46.8 ms
64 bytes from ( icmp_seq=3 ttl=58 time=46.6 ms
64 bytes from ( icmp_seq=4 ttl=58 time=46.8 ms
--- ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3013ms
rtt min/avg/max/mdev = 46.562/46.689/46.827/0.110 ms

Note that we are able to run the ifconfig, ip and ping commands into this container without having to install it. If the container was based on the ubuntu base image, these commands would not have been available, needing an installation.


So that was a simple and quick introduction on how to use a Dockerfile to create your own custom images with packages pre-installed. However installing packages is not the only reason we create custom images. Dockerfile can be used to run a variety of commands to modify the images in a million ways. To learn more about what Dockerfile can do, check out the Dockerfile reference documentation.

In upcoming articles we shall explore docker further and learn more things that can be done with this powerful tool Docker. Let us know your thoughts in the comments below.

About Silver Moon

A Tech Enthusiast, Blogger, Linux Fan and a Software Developer. Writes about Computer hardware, Linux and Open Source software and coding in Python, Php and Javascript. He can be reached at [email protected].

Leave a Reply

Your email address will not be published. Required fields are marked *