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 silver@silver-VirtualBox:~/docker$
4. Launch container using your image
Launch the container with the usual run operation.
$ sudo docker run -it my_docker_image root@77201f7b8ec4:/#
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 silver@silver-VirtualBox:~/docker$
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 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255 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 127.0.0.1 netmask 255.0.0.0 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 127.0.0.1/8 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 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever root@8d0773ec7ade:/# ping google.com PING google.com (142.250.193.110) 56(84) bytes of data. 64 bytes from maa05s24-in-f14.1e100.net (142.250.193.110): icmp_seq=1 ttl=58 time=46.6 ms 64 bytes from maa05s24-in-f14.1e100.net (142.250.193.110): icmp_seq=2 ttl=58 time=46.8 ms 64 bytes from maa05s24-in-f14.1e100.net (142.250.193.110): icmp_seq=3 ttl=58 time=46.6 ms 64 bytes from maa05s24-in-f14.1e100.net (142.250.193.110): icmp_seq=4 ttl=58 time=46.8 ms ^C --- google.com 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 root@8d0773ec7ade:/#
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.
Conclusion
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.