Docker

What is a Docker container and How to use it

In this article, we will see what is a Docker container and how to use it.

1. Introduction

Containers, in general, and Docker in particular is quickly gaining in popularity.  In this post, we will get some basics sorted out about the Docker container.  We will learn the meaning of the term Container in general and see specifically how Docker implements them.  We will try to execute a few basic commands to understand the bare basics of Docker itself, Docker images, and Docker containers. So, let’s start.

2. Containers, what are they?

Operating system designers have always striven to provide secure operating environments to applications running on them. While applications need the harness the complete power of the underlying processing power and hardware for their work, they also need to be secured from harming other running applications. Over time, operating systems have progressed to provide features to virtualize several aspects of the underlying hardware – memory, file-systems, network interfaces, process trees, etc.

 
These virtualization efforts diverged primarily into 2 directions (among many of course) – hardware virtualization and operating system (OS) level virtualization. Hardware virtualization refers to the creation of virtual machines (on a host OS or even further lower) that virtualizes the underlying physical machine. Users can install complete OS into such virtual machines. OS level virtualization refers to features provided in the kernel (of the host OS) that allows creation of virtual and isolated hosting environments in the user space of the host OS. These virtual hosting environments are also termed Containers.

Docker container - Hardware virtualization and OS level virtualization
Hardware virtualization and OS-level virtualization

Over time there have been several such Container technologies that enabled containers to be created in the user space – chroot, jail, Oracle Solaris Zones, LXC, Docker

3. Docker – What is it?

So now, what is Docker? Simply put, Docker is a container technology that enables users to deploy their applications into containers and run them within those containers. Docker enables users to easily package their applications and all of their dependencies into read-only images called Docker images. These images can be easily distributed through a Docker hub. These images can then be downloaded and executed in Docker Containers.

Docker container - Basic workflow while using Docker
Basic workflow while using Docker

Now that we are briefly introduced to Docker, let us next discuss how we can install Docker and use it to create new containers.

4. Install Docker

Docker can be installed on Linux, Mac, and Windows. Please follow the installation instructions for Docker docs to get them installed in your machine.  Once installed you can check if it is installed properly by running a Hello world container.  Please follow this post to explore a few ways to do so.  Now that Docker is installed, let us play around with the basics.

5. Basic commands to understand your Docker installation

We can get overall information from Docker using the command:

$ docker info
Docker container - dokcer info
The output of the command “docker info”

As you can see from the top 3 lines of the output, the number of total containers and images is 0 since we have a fresh install of Docker.

Once installed you can check the Docker images present using the command

$ docker images
Docker container - Output of Docker images after a fresh Docker install
The output of Docker images after a fresh Docker install

It will be empty since we have installed Docker just now.

Once installed you can check the Docker containers present using the command

$ docker ps
Docker container - Output of docker ps for a fresh Docker install
The output of docker ps for a fresh Docker install

It will be empty since we have installed Docker just now.  Now let us create a container and try to understand how Docker behaves.

6. Basic Docker commands to create containers and images

Let us suppose that we want to create a separate container running Alpine Linux. To do this we simply pull down the official Alpine Docker image shared in the Docker Hub.

$ docker pull alpine:latest
Docker container - Output of docker pull
The output of docker pull

If you do not have this Docker image already then you will see a similar output like below. Otherwise, you will get a message saying you already have this image. Let us see what Docker tells us about the number of images available locally now.

$ docker info
Docker container - Partial output of docker info after pulling an image
The partial output of docker info after pulling an image

Now you can see that the number of images is 1 – the Alpine image we just downloaded. However, the number of Containers is still 0 since we have not spawned a new container yet. This image gets stored locally in the folder /var/lib/docker/image

Docker container - Local location of downloaded Docker images in /var/lib/docker
Local location of downloaded Docker images in /var/lib/docker

We can see that the SHA256 id listed is the same as in the output for docker images.

To use this image, we need to create a new container out of this image. We do that by the docker run command.

$ docker run alpine:latest ls -l
Docker container - Run the Alpine image using docker run
Run the Alpine image using docker run

So we spawned a new container out of the alpine image and ran the command ls -l within this container. What you see is the output of this command within the virtual environment provided by the container. By using the docker info command we can see that the number of containers has increased by 1.

Output of docker info after creating a container
The output of docker info after creating a container

Specifically, we see from the above output that the number of stopped containers has increased by 1. That is because when we spawned a new container above from the Alpine image, a new container was created and then the ls -l command was run and then the container was stopped. We can see all containers that are created by the docker ps command like so

Output of docker ps --all
The output of docker ps –all

From above, there is now one container created from the Alpine image and it has exited now.

Now let us see what happens if we use the same Alpine image to run another command in a container.

$ docker run alpine:latest echo "hello world"
Output of docker run on Alpine image
The output of docker run on Alpine image

“hello world” was printed as instructed. But let us see how many containers are present now

$ docker ps --all
Output of docker ps
Output of docker ps

We can see that Docker created a new container from the same image to execute the new command. So Docker spawns a new container from an image for every operation unless we explicitly ask it to use the same container to execute different commands.

Now let us try to understand more about the Docker image we created

$ docker inspect alpine:latest
[
    {
        "Id": "sha256:baa5d63471ead618ff91ddfacf1e2c81bf0612bfeb1daf00eb0843a41fbfade3",
        "RepoTags": [
            "alpine:latest"
        ],
        "RepoDigests": [
            "alpine@sha256:1354db23ff5478120c980eca1611a51c9f2b88b61f24283ee8200bf9a54f2e5c"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2016-10-18T20:31:22.321427771Z",
        "Container": "1d811a9194c47475510bc53700001c32f2b0eb8e3aca0914c5424109c0cd2056",
        "ContainerConfig": {
            "Hostname": "1d811a9194c4",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ADD file:7afbc23fda8b0b3872623c16af8e3490b2cee951aed14b3794389c2f946cc8c7 in / "
            ],
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "DockerVersion": "1.12.1",
        "Author": "",
        "Config": {
            "Hostname": "1d811a9194c4",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": null,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 4799225,
        "VirtualSize": 4799225,
        "GraphDriver": {
            "Name": "devicemapper",
            "Data": {
                "DeviceId": "7",
                "DeviceName": "docker-253:3-135839549-8cb0423f038b9f8d5f1b1a28bc13ba1a32ec373994d46b41b299774aa15e2b12",
                "DeviceSize": "10737418240"
            }
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:011b303988d241a4ae28a6b82b0d8262751ef02910f0ae2265cb637504b72e36"
            ]
        }
    }
]

That is a lot of information we get about the image there. We will not discuss them in detail here but you can see that the image id, tag, and time-stamp of creation are obvious there. Any containers created from this image are listed next with details of that container. The Docker version used and the Author of the image are also listed. Then follow further details of the image, its architecture of the virtual hosting environment, and finally the emulated root file-system for that image.

Now let us try to understand more about the container we created from this image. Let us see what all containers we have at present.

$ docker ps --all
Output of docker ps
The output of docker ps

We see that the containers adoring_bell and desperate_poincare is based on the image alpine:latest. Let us see the metadata of this container more.

$ docker inspect adoring_bell
[
    {
        "Id": "c1631bed732bac64355c652ef0b82876b5c7c8a89c3dfe45a4155e1148d5e46b",
        "Created": "2016-11-08T10:38:04.90010304Z",
        "Path": "echo",
        "Args": [
            "hello world"
        ],
        "State": {
            "Status": "exited",
            "Running": false,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 0,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2016-11-08T10:38:05.582857658Z",
            "FinishedAt": "2016-11-08T10:38:05.610419062Z"
        },
        "Image": "sha256:baa5d63471ead618ff91ddfacf1e2c81bf0612bfeb1daf00eb0843a41fbfade3",
        "ResolvConfPath": "/home/hariharan/work/docker-home/containers/c1631bed732bac64355c652ef0b82876b5c7c8a89c3dfe45a4155e1148d5e46b/resolv.conf",
        "HostnamePath": "/home/hariharan/work/docker-home/containers/c1631bed732bac64355c652ef0b82876b5c7c8a89c3dfe45a4155e1148d5e46b/hostname",
        "HostsPath": "/home/hariharan/work/docker-home/containers/c1631bed732bac64355c652ef0b82876b5c7c8a89c3dfe45a4155e1148d5e46b/hosts",
        "LogPath": "/home/hariharan/work/docker-home/containers/c1631bed732bac64355c652ef0b82876b5c7c8a89c3dfe45a4155e1148d5e46b/c1631bed732bac64355c652ef0b82876b5c7c8a89c3dfe45a4155e1148d5e46b-json.log",
        "Name": "/adoring_bell",
        "RestartCount": 0,
        "Driver": "devicemapper",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": null,
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DiskQuota": 0,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": -1,
            "OomKillDisable": false,
            "PidsLimit": 0,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0
        },
        "GraphDriver": {
            "Name": "devicemapper",
            "Data": {
                "DeviceId": "11",
                "DeviceName": "docker-253:3-135839549-71e94f8b4e5e1af0b31889245ea0d2ffa54c68e6991399d0438f473846f701cc",
                "DeviceSize": "10737418240"
            }
        },
        "Mounts": [],
        "Config": {
            "Hostname": "c1631bed732b",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": true,
            "AttachStderr": true,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "echo",
                "hello world"
            ],
            "Image": "alpine:latest",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "27ee3cf100495a82743f1ec700195a4e560a04e0164a65976774ea23bb515450",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": null,
            "SandboxKey": "/var/run/docker/netns/27ee3cf10049",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "",
            "Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "",
            "IPPrefixLen": 0,
            "IPv6Gateway": "",
            "MacAddress": "",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "efd29ba943ae4030c14b6e88fda502ff260a490ae5d264e9626d3882d7358195",
                    "EndpointID": "",
                    "Gateway": "",
                    "IPAddress": "",
                    "IPPrefixLen": 0,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": ""
                }
            }
        }
    }
]

There is a wealth of info there. We will not get into the details but discuss it overall here. The first 5 entries give the container id, time-stamp at container creation, the command sent to the container at the start (echo “hello world”), and the current state of the container (it has exited now). Next follows the details of the image from which the container was spawned and where it is mapped into the local file system. The container name assigned is seen (adoring_bell). Then follows the various details of the emulated virtual environments – the virtual networks, virtual mount points, etc.,

7. Summary

We covered some very basic understanding of what a Docker container is in this post. We were introduced to the concept of OS-level virtualization and a software container. Then we covered some basic commands to download Docker images and spawn new containers from the image. Later we covered some commands to inspect the metadata that Docker holds about itself, the images, and the containers.

Last updated on Jan. 31st, 2022

Hariharan Narayanan

Hari graduated from the School of Computer and Information Sciences in the University of Hyderabad. Over his career he has been involved in many complex projects in mobile applications, enterprise applications, distributed applications, micro-services, and other platforms and frameworks. He works as a consultant and is mainly involved with projects based on Java, C++ and Big Data technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button