The Ultimate Docker Handbook — Part One: Basics
According to IBM:
Containerization involves encapsulating or packaging up software code and all its dependencies so that it can run uniformly and consistently on any infrastructure.
In other words, containerizing an application means that we pack all the code, the libraries and dependencies in a self-contained package so that it can be run without going through a troublesome setup process.
Let us consider a real life scenario: we have an application running PHP-FPM, Node-JS, to compile some assets and some kind of database like for example MySql, Mongo or Redis for caching.
Everytime you have to update some of the versions of this softwares, it could lead to some pain (and by saying some, i am very gentle).
Let’s assume that you’ve gone through all the hassle of setting up the dependencies and have started working on the project. Does that mean you’re out of danger now? Of course not.
What if you have a teammate who uses Windows while you’re using Linux? Now you have to consider the inconsistencies of how these two different operating systems handle paths. Or the fact that popular technologies like nginx are not well optimized to run on Windows. Some technologies like Redis don’t even come pre-built for Windows.
Even if you get through the entire development phase, what if the person responsible for managing the servers follows the wrong deployment procedure?
The solution to that would be:
- Wrap up an application in such a way that its deployment and runtime issues is consistent across all “containerized” apps.
- Share that image and make it accessible by anyone with proper authorization.
This is the idea behind containerization: to put an app inside a container, and ship it across all environments running just a few scripts.
What is Docker?
Docker is an open-source platform that allows you to containerize applications and share them using public or private registries, and also to orchestrate them.
What is the orchestration of containes? According to Red Hat Foudation:
We call container orchestration the automation of container deployment, management, scalability, and networking processes. It is a highly functional methodology for companies that need to deploy and manage hundreds or thousands of Linux hosts and containers.
What is a container?
According to the Docker official website:
A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings.
As we never stress enough, one could think to a container just like a virtual machine, but it is incorrect.
The difference beetween the two are the ways of virtualization.
Virtual machines are usually created and managed by a program known as a hypervisor, like for example VirtualBox. The hypervisor is set between the host operating system and the virtual machines and takes care of communications.
Containers on the other hand share the machine’s OS system kernel and therefore do not require an OS per application, driving higher server efficiencies and reducing server and licensing costs.
What is a Docker image?
A Docker image is a file used to execute code in a Docker container. Docker images act as a set of instructions to build a Docker container, like a template. Docker images also act as the starting point when using Docker. An image is comparable to a snapshot in virtual machine (VM) environments. You can think to images as a read-only snapshot of a system or application.
Containers are just images in running state. When you obtain an image from the internet and run a container using that image, you essentially create another temporary writable layer on top of the previous read-only ones.
What is a docker registry?
An image registry is a centralized repository where people can upload images or pull others. Docker Hub is the default public registry for Docker. Red Hat with Quay, AWS with ECR provide others, for example.
There is also a local registry that runs within your computer that caches images whenever ypu pull one from remote registries.
How to Install Docker on Linux
Installing Docker is very simple, it only requires to follow the steps of the official documentaton.
- Install Docker on Ubuntu.
- Install Docker Engine on Debian
- Install Docker Engine on Fedora
- Install Docker Engine on CentOS
Also, documentaton for Mac or Windows installation is available on the site. In this Handbook we are going to assume that the user is working on an Ubuntu Linux machine.
How to install Docker Compose
In order to use all the functionalitites provided by the Docker automations, you’ll have to install Docker Compose. As said before, just follow the Install Docker Compose guide from the official docs.
When you’re finished, just run the commands below to be sure the installation went well:
docker --version
docker-compose --version
How to run containers
As a first command we are just going to run a simple container, this container is the so called “whalesay”.
Actually this is the Docker version of an old nerdy joke of the 80’s called the “cowsay”, it was just a program that accepted a string as a param before printing it to the standard output (the terminal) a cow with a balloon with the string passed as param. well, this version does pretty much the same thing, but it prints a whale, of course.
mattia@pop-os:~$ docker run docker/whalesay cowsay hi-guysUnable to find image 'docker/whalesay:latest' locallylatest: Pulling from docker/whalesayImage docker.io/docker/whalesay:latest uses outdated schema1 manifest format. Please upgrade to a schema2 image for better future compatibility. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/e190868d63f8: Pull complete909cd34c6fd7: Pull complete0b9bfabab7c1: Pull completea3ed95caeb02: Pull complete00bf65475aba: Pull completec57b6bcc83e3: Pull complete8978f6879e2f: Pull complete8eed3712d2cf: Pull completeDigest: sha256:178598e51a26abbc958b8a2e48825c90bc22e641de3d31e18aaf55f3258ba93bStatus: Downloaded newer image for docker/whalesay:latest
this command will generate this result in the terminal:
the docker run command will run immediately a container if its image exists on the host if not, it will try to pull the image from Docker Hub, the docker public registry, and then will run it.
Listing and removing containers and images
As we saw before, to run a container we can use:
docker container run <image-name> <options>
we will see options in detail later, for now let us just focus on other basic commands.
docker ps
the command we just saw will list all the containers active and running on the host, while using:
docker ps -a
will list all the containers present on the host, running or not.
Always remember it is always a good measure to remove images and containers you do not use. Let us remove our whalesay container, first, let’s list all of the containers in our host:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fde0b1f6cbb6 docker/whalesay "cowsay hi-guys" 2 days ago Exited (0) 2 days ago stupefied_satoshi
89a50f182df6 ubuntu "bash" 2 days ago Exited (0) 2 days ago magical_clarke
bc27dfd390ee ubuntu "sleep 5" 2 days ago Exited (0) 2 days ago silly_davinci
c7e130b21590 ubuntu "cmd sleep 5" 2 days ago Created nostalgic_ardinghelli
94e308588070 ubuntu "bash" 2 days ago Exited (0) 2 days ago priceless_tereshkova
f3fa23cb1457 sail-8.0/app "start-container" 6 days ago Exited (255) 2 days ago 0.0.0.0:80->80/tcp, :::80->80/tcp, 8000/tcp hct-formation_laravel.test_1
888e9e4d0269 selenium/standalone-chrome "/opt/bin/entry_poin…" 6 days ago Exited (255) 2 days ago 4444/tcp hct-formation_selenium_1
62238c870d71 mysql:8.0 "docker-entrypoint.s…" 6 days ago Exited (255) 2 days ago 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp hct-formation_mysql_1
360e828e3cce redis:alpine "docker-entrypoint.s…" 6 days ago Exited (255) 2 days ago 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp hct-formation_redis_1
4b8c766a6d64 getmeili/meilisearch:latest "tini -- /bin/sh -c …" 6 days ago Exited (255) 2 days ago 0.0.0.0:7700->7700/tcp, :::7700->7700/tcp hct-formation_meilisearch_1
c1f15cc6e986 mailhog/mailhog:latest "MailHog" 6 days ago Exited (255) 2 days ago 0.0.0.0:1025->1025/tcp, :::1025->1025/tcp, 0.0.0.0:8025->8025/tcp, :::8025->8025/tcp hct-formation_mailhog_1
e5bb5102f1ec 08fe36736048 "docker-php-entrypoi…" 5 months ago Exited (255) 4 months ago 9000/tcp phpdev
pretty confusing. But we have different options tha could come in handy, for example we can filter or pretty print informations in a template, the reference guide provides different examples.
let us print informations in a better way:
mattia@pop-os:~$ docker ps -a --format "{{.ID}}: {{.Names}} -- {{.Size}}"
fde0b1f6cbb6: stupefied_satoshi -- 0B (virtual 247MB)89a50f182df6: magical_clarke -- 0B (virtual 72.8MB)bc27dfd390ee: silly_davinci -- 0B (virtual 72.8MB)c7e130b21590: nostalgic_ardinghelli -- 0B (virtual 72.8MB)94e308588070: priceless_tereshkova -- 0B (virtual 72.8MB)f3fa23cb1457: hct-formation_laravel.test_1 -- 441kB (virtual 739MB)888e9e4d0269: hct-formation_selenium_1 -- 1.34MB (virtual 1.05GB)62238c870d71: hct-formation_mysql_1 -- 7B (virtual 514MB)360e828e3cce: hct-formation_redis_1 -- 0B (virtual 32.3MB)4b8c766a6d64: hct-formation_meilisearch_1 -- 0B (virtual 319MB)c1f15cc6e986: hct-formation_mailhog_1 -- 0B (virtual 392MB)e5bb5102f1ec: phpdev -- 0B (virtual 160MB)
All containers when created have an ID, and a Name, let us remove our stupified_satoshi container:
mattia@pop-os:~$ docker rm stupefied_satoshi
stupefied_satoshi
the syntax for the remove command is:
docker rm <name or id>
In this case we did not need to stop the container, but in case we need to do it, instead of removing:
docker stop <name or id>
let’s remove the image, but before let us list them:
mattia@pop-os:~$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEsail-8.0/app latest 1caf2d89b56c 2 weeks ago 739MBubuntu 21.04 e5ec58a9f549 2 weeks ago 80MBubuntu latest fb52e22af1b0 2 weeks ago 72.8MBgetmeili/meilisearch latest 258fa3aa1230 2 weeks ago 319MBselenium/standalone-chrome latest 71b7222c4f82 2 weeks ago 1.05GBredis alpine f6f2296798e9 3 weeks ago 32.3MBmysql 8.0 5a4e492065c7 4 weeks ago 514MB<none> <none> 08fe36736048 5 months ago 160MBlaravelsail/php80-composer latest b7a5343314d6 9 months ago 458MBmailhog/mailhog latest 4de68494cd0d 13 months ago 392MB<none> <none> 72f169b8ac73 13 months ago 945MBdocker/whalesay latest 6b362a9f73eb 6 years ago 247MB
now let’s remove our docker/whalesay image:
mattia@pop-os:~$ docker rmi docker/whalesay:latestUntagged: docker/whalesay:latestUntagged: docker/whalesay@sha256:178598e51a26abbc958b8a2e48825c90bc22e641de3d31e18aaf55f3258ba93bDeleted: sha256:6b362a9f73eb8c33b48c95f4fcce1b6637fc25646728cf7fb0679b2da273c3f4Deleted: sha256:34dd66b3cb4467517d0c5c7dbe320b84539fbb58bc21702d2f749a5c932b3a38Deleted: sha256:52f57e48814ed1bb08a651ef7f91f191db3680212a96b7f318bff0904fed2e65Deleted: sha256:72915b616c0db6345e52a2c536de38e29208d945889eecef01d0fef0ed207ce8Deleted: sha256:4ee0c1e90444c9b56880381aff6455f149c92c9a29c3774919632ded4f728d6bDeleted: sha256:86ac1c0970bf5ea1bf482edb0ba83dbc88fefb1ac431d3020f134691d749d9a6Deleted: sha256:5c4ac45a28f91f851b66af332a452cba25bd74a811f7e3884ed8723570ad6bc8Deleted: sha256:088f9eb16f16713e449903f7edb4016084de8234d73a45b1882cf29b1f753a5aDeleted: sha256:799115b9fdd1511e8af8a8a3c8b450d81aa842bbf3c9f88e9126d264b232c598Deleted: sha256:3549adbf614379d5c33ef0c5c6486a0d3f577ba3341f573be91b4ba1d8c60ce4Deleted: sha256:1154ba695078d29ea6c4e1adb55c463959cd77509adf09710e2315827d66271a
Pulling images
With the pull command, you will be able to pull an image without actually starting a container. Let us pull an image, we will use it later:
docker pull kodekloud/simple-webapp
the syntax for the command is:
docker pull <image-name>:<tag>
if you go and check on DOcker hub, you will see that almost every image has different names. If for example we want to get an image with Redis installed, we will see different tags, corresponding to different versions of the service. If we run or pull without specifying a tag, the latest version will be automatically selected.
Mapping ports, detached mode and getting container infos
Let us now pull and run an example image provided by KodeKloud, it will run a simple web app on our host, and expose to incoming http requests the 8080 port:
mattia@pop-os:~$ docker run kodekloud/simple-webappThis is a sample web application that displays a colored background.A color can be specified in two ways.1. As a command line argument with --color as the argument. Accepts one of red,green,blue,blue2,pink,darkblue2. As an Environment variable APP_COLOR. Accepts one of red,green,blue,blue2,pink,darkblue3. If none of the above then a random color is picked from the above list.Note: Command line argument precedes over environment variable.No command line argument or environment variable. Picking a Random Color =blue2* Serving Flask app "app" (lazy loading)* Environment: productionWARNING: Do not use the development server in a production environment.Use a production WSGI server instead.* Debug mode: off* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
thing is, that if we go to our localhost at port 8080, our application won’t be accessible at all! What is happening here? No panic.
As we said during the first phases of this intro, containers are isolated, and that means that they are not accessible from the outside. When we expose the port of a container, by default, this port will be accessible only from the local host subnetwork. We can search for the ip address of the container, because yes, they will get one, but it will be available only in the host network.
Before doing that, we must face another problem: our terminal is stuck, and if we run ctrl+C, we will stop the app. In order to keep the terminal free, we must use the detached mode, when running a container, we can always use the — detached flag to do that:
docker run -detached kodekloud/simple-webapp
to get now te informations about te container, let us use the inspect command, and the name of the container:
docker inspect upbeat_lehmann
the upword command will print all the infos of the container in a JSON format, if we search for the “Gateway” key, we can get the internal ip address of the container, and we can access to it:
Now, we want to access that container (and app) from the localhost, and if we use an app on the server, event from outside, so how can we do it? By mapping (or in a more right term by forwarding) the host port to the container port. Let us forward the port 80 of the host to the 8080 of the container:
docker run -detached -p 80:8080 kodekloud/simple-webapp
Now we can access the app from te localhost (and from the outside if we have a server, since the port 80 of our server will forward the request to the 8080 of the container.
Understanding the lifecycle of a container
With this last paragraph we end tis first part of this series about Docker. Let us now run subsequently this different commands and observe the different output:
mattia@pop-os:~$ docker run -detached redhat/ubi8
3a68d7305421ef52218632b603e730a3d3a68d7724fdf08d41b4c48f2b13cf15
mattia@pop-os:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
mattia@pop-os:~$ docker run -detached redhat/ubi8 sleep 15
e0873f30a3bba57318e96e68b5b718c11d19aaaa88f3d60f604479d9443093d8mattia@pop-os:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e0873f30a3bb redhat/ubi8 "sleep 15" 3 seconds ago Up 1 second distracted_carver
the — detached flag provides a way to run containers in detached mode, meaning you will have the terminal free to execute other commands, and all the outputs from the container will be ignored.
In the first case we used the run command to start a Red Hat Enterprise Linux container, but just after the start, if we list the active containers, we will see a void response. In the second case we provide a sleep command, in bash, the sleep command will freeze the terminal for the given number of seconds, after that, if we run the container in detached mode, we will be able to list again, and we will see that the container is now active.
This leads us to a very important concept in containerization: containers lives as long as they have an active service or process, then they will be destroyed. This means that if a container does not have an active process, or if the service or process it was running dies or crash, this will lead to the destruction of the container. This is a basic and vital concept, and we will stress enough during next articles.
What’s next?
In this article we provided a simple and easy walkthrough of the basics of docker, in next articles we will dive deeply into containerization, and we wll learn how to intercat with multiple containers, construct subnetworks, build our own apps with docker and pack them, and how to deploy them on multiple cloud hosting environments like Digital Ocean, AWS or Azure.
Hope this first article was good for you, it will be an exciting and long journey.
Stay safe.