What are Docker Containers?
The concept of containerization gave birth to containers. Therefore, the concept of containers is very fundamental to the world of containerization.
A container is a runnable instance of an image. A docker container can be created, started, stopped, moved, or deleted using the Docker CLI Client or any other accepted clients. You can combine multiple containers to become one or attach one or more networks, storage, or create a new image based on the current state of the container.
If you know virtual machines, then you may consider containers to be equivalent to modern virtual machines.
Docker containers are completely isolated environments from the host machine as well as other containers. They are built to be a more lightweight, standalone, executable package of software that includes everything needed to run an application which is the codes, runtimes, system tools, system libraries, or settings.
Let me work you through the diagram a little deeper to understand Docker containers from the ground up.
Docker containers are lightweight because they 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.
As you can see in the diagram above, each container relies on the Docker layer which in turn uses the resources from the machine’s OS.
This is one of the differences between virtual machines and Docker containers, and also a great benefit of using Docker containers because a virtual machine relies on individual Guest OS for each application as shown in the image below:
Now that we understand Docker containers, let’s look at how to manipulate and manage a Docker container in the next lesson.
How to Run a Container
When we learned how to run Docker images, we used the docker run
command to achieve it. The command is also used to create and start a container by specifying the image name and some optional options as shown below:
docker run image-name
This command will create and start a container if the image name is specified correctly and it exists anywhere in your local machine or Docker registry.
However, Docker has updated this command and made it easy to understand thereby improving developer experiences. The structure of the new command syntax looks like this:
docker <object> <command> <options>
Where:
<object>
is the type of Docker object you'll be manipulating. This can be a container
, image
, network
, or volume
object.
<command>
indicates the task to be carried out by the daemon, that is the run
command.
<options>
can be any valid parameter that can override the default behavior of the command, like the -publish
option for port mapping.
Now the complete syntax to start and run a container from an image will look like this:
docker container run image-name
Let’s replace the image-name
with something more practical, let’s run the nginx
container as an example. To run a container using this image, execute the following command on your terminal:
docker container run --publish 8080:80 nginx
This will result in an output such as:
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
3ae0c06b4d3a: Pull complete
efe5035ea617: Pull complete
a9b1bd25c37b: Pull complete
f853dda6947e: Pull complete
38f44e054f7b: Pull complete
ed88a19ddb46: Pull complete
495e6abbed48: Pull complete
Digest: sha256:08bc36ad52474e528cc1ea3426b5e3f4bad8a130318e3140d6cfe29c8892c7ef
Status: Downloaded newer image for nginx:latest
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/07/06 12:51:31 [notice] 1#1: using the "epoll" event method
2023/07/06 12:51:31 [notice] 1#1: nginx/1.25.1
2023/07/06 12:51:31 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2023/07/06 12:51:31 [notice] 1#1: OS: Linux 5.15.49-linuxkit
2023/07/06 12:51:31 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/07/06 12:51:31 [notice] 1#1: start worker processes
2023/07/06 12:51:31 [notice] 1#1: start worker process 29
2023/07/06 12:51:31 [notice] 1#1: start worker process 30
2023/07/06 12:51:31 [notice] 1#1: start worker process 31
2023/07/06 12:51:31 [notice] 1#1: start worker process 32
2023/07/06 12:51:31 [notice] 1#1: start worker process 33
Also, notice that we have a new option added to the command, the --publish
option. Let’s talk about that in the next section and other popular options you can use with your Docker container command.
How to Publish a Port
As you already know, Docker containers are isolated environments. Your host system (local machine) does not know anything that’s going on inside the container environment unless you expose it.
Therefore, one way to expose the running application inside your container is by exposing the port to your host system.
Let’s say, for example, you started an Express application inside your container on port 3000
, your host machine will not know that port 3000
is running your Express application unless you explicitly expose it when you want to create the container using the command below:
docker container run --publish 3000:3000 my-express-app
When you write --publish 3000:3000
, it meant any request sent to port 3000
of your host system will be forwarded to port 3000
inside the container.
The process is called port mapping, you can map your container port to a different and available port on your local machine as shown in this example:
docker container run --publish 4000:3000 my-express-app
In this case, if you visit localhost:3000
, it will not work because your container port is mapped to 4000
on your local machine and it’s expecting traffic from localhost:4000
.
Also, you can use a shorthand version of the command option as shown below which means the same thing:
docker container run -p 4000:3000 my-express-app
How to Use Detached Mode
Next, you can run your container commands in detached mode meaning that your command will run in the background and your terminal will be wide open for new commands.
Here’s a command and the option to do so:
docker container run --detach --publish 4000:3000 my-express-app
or the shorthand version:
docker container run -d -p 4000:3000 my-express-app
How to List Containers
The next command is the container ls
command, which allows you to list all available containers in your local machine that are currently running.
If you have any container in your local machine running, it should show you a list of them as shown below:
docker container ls
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 9f21cb888058 my-express-app "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp upbeat_burn
To list all your containers including all states such as running, stopped, etc. Use the command shown in the example below:
docker container ls --all
// or the short-hand version
docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f21cb888058 my-express-app "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp upbeat_burn
ae9192b8e32d node "docker-entrypoint.s…" 9 minutes ago Exited (0) 6 minutes ago upbeat_blackburn
A container named upbeat_burn
is running. It was created 5 seconds ago
and the status is Up 5 seconds,
which indicates that the container has been running fine since its creation.
The CONTAINER ID
is 9f21cb888058
which is the first 12 characters of the full container ID. The full container ID is 9f21cb88805810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc
which is 64 characters long. This full container ID was printed as the output of the docker container run
command in the previous section.
How to Name or Rename a Container
Every container has two identifiers by default which are:
CONTAINER ID
- a random 64-character-long string.
NAME
- a combination of two random words, joined with an underscore.
You can rename a container to your user-defined name which will be easy to refer to than using the two randomly generated identifiers.
You can rename a container using the --name
option or -n
for short. To run another container using the my-express-app
image with the name my-express-app-container
you can execute the following command:
docker container run --detach --publish 4000:3000 --name my-express-app-container my-express-app
A new container with the name my-express-app-container
will be started.
You can even rename old containers using the container rename
command. The syntax for the command is as follows:
docker container rename <container identifier> <new name>
Here's an example:
docker container rename my-express-app my-express-app-container-2
The command doesn't yield any output but you can verify that the changes have taken place using the container ls
command. The rename
command works for containers both in the running state and the stopped state.
How to Stop or Kill a Running Container
To stop a running container is easy. You can use the ctrl + c
command to stop a running container on your terminal.
However, if you’re container is running in a detached mode, you need to use the following command to stop it.
Below is a generic syntax for the command is as follows:
docker container stop <container identifier>
Where the container identifier
can either be the id or the name of the container. You can get the identifier with the docker container ls
command. Next, here’s an example to stop a running container.
docker container stop my-express-app-container
# my-express-app-container
If you use the name as an identifier, you'll get the name thrown back to you as output. The stop
command shuts down a container gracefully by sending a SIGTERM
signal. If the container doesn't stop within a certain period, a SIGKILL
signal is sent which shuts down the container immediately.
In cases where you want to send a SIGKILL
signal instead of a SIGTERM
signal, you may use the container kill
command instead. The container kill
command follows the same syntax as the stop
command.
docker container kill my-express-app-container
How to Restart a Container
Restarting a container can happen in two ways:
Restarting the container from a failed, stopped, or killed state.
Rebooting a container from a running state.
You can start a container from a failed, stopped, or killed state using the command below:
docker container start my-express-app-container
Next, you can reboot a running container using the following command:
docker container restart my-express-app-container
The main difference between the two commands is that the container restart
command attempts to stop the target container and then starts it back up again, whereas the start
command just starts an already stopped container.
In the case of a stopped container, both commands are exactly the same. But in the case of a running container, you must use the container restart
command.
How to Create a Container Without Running
Sometimes, you just want to create a container without running it. You can achieve this using the following command:
docker container create --publish 4000:3000 my-express-app-container
The STATUS
of the container is Created
at the moment, and, given that it's not running, it won't be listed without the use of the --all
option in the docker container ls
command.
The create
command will create a new container and store it inside your local machine without running it. So that you can use the docker container start
command to start the container next time without creating it again.
How to Remove Containers
Sometimes, you want to remain unused containers since all containers including stopped and killed containers still remain in your local system. Removing unused containers can save your system memory.
The following command is used to remove any container by passing in the identifier of the container you want to delete.
Here’s the syntax:
docker container rm <container identifier>
You can use the docker container ls -all
command to get the identifier.
docker container rm 6cf52771dde1
# 6cf52771dde1
This command will delete the container whose identifier is specified as seen above.
In addition, you can use the --rm
option to indicate a one-time container meaning that you want the container to be deleted immediately after they are stopped. You can use the --rm
option in both start
and run
commands. Let’s look at this example with the run
command.
docker container run --rm --detach --publish 5000:3000 --name my-express-app-container-one-time my-express-app-container
The my-express-app-container-one-time
container will be deleted immediately after it is stopped or killed.
How to Run a Container in Interactive Mode
Some images come with different lightweight versions of operating systems such as Linux and different distributions such as Ubuntu, Fedora, Kali, etc. If you use any of these images that come with an operating system. You can interact with the inside of the operating system when running the container.
For instance, Programming languages such as python, php, go, or run-times like node and deno all have their official images.
These images do not just run some pre-configured program. They are instead configured to run a shell by default. In the case of the operating system images, it can be something like sh
or bash
and in the case of the programming languages or runtimes, it is usually their default language shell.
For example, if you are using the Ubuntu image, you can interact with the Ubuntu shell to install programs, navigate to folders or create a new file. In the same way, if you’re using a Node.js image, you might want to interact with the default language shell.
To achieve this, Docker provides the interactive --interactive
or -it
option which allows you to interact with the shell of your image when starting or running a container.
Here’s an example with an Ubuntu image:
docker container run --rm -it ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
5af00eab9784: Pull complete
Digest: sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
Status: Downloaded newer image for ubuntu:latest
root@09d3c433639d:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="<https://www.ubuntu.com/>"
SUPPORT_URL="<https://help.ubuntu.com/>"
BUG_REPORT_URL="<https://bugs.launchpad.net/ubuntu/>"
PRIVACY_POLICY_URL="<https://www.ubuntu.com/legal/terms-and-policies/privacy-policy>"
UBUNTU_CODENAME=jammy
root@09d3c433639d:/#
The -it
option sets the stage for you to interact with any interactive program inside a container. This option is actually two separate options mashed together.
The i
or -interactive
option connects you to the input stream of the container so that you can send inputs to bash.
The t
or -tty
option makes sure that you get some good formatting and a native terminal-like experience by allocating a pseudo-tty.
You need to use the -it
option whenever you want to run a container in interactive mode. Another example can be running the node
image as follows:
docker container run -it node
Unable to find image 'node:latest' locally
latest: Pulling from library/node
42cbebf8bc11: Pull complete
9a0518ec5756: Pull complete
356172c718ac: Pull complete
dddcd3ceb998: Pull complete
abe47058ac42: Pull complete
08ff2ee7b183: Pull complete
70b6353bf75e: Pull complete
2742b73156b1: Pull complete
Digest: sha256:57391181388cd89ede79f371e09373824051eb0f708165dcd0965f18b3682f35
Status: Downloaded newer image for node:latest
Welcome to Node.js v20.3.1.
Type ".help" for more information.
> 5 + 5
10
>
How to Execute Commands Inside a Container
You can execute a command inside your container, assuming you want to execute a command that is not available in Windows operating while you’re using Windows at the moment.
You can easily spin up a container that uses a Linux operating system and execute the command immediately. You can even make it a one-time container as discussed above just to help you achieve a specific task in Linux.
For example, assume that you want to encode a string using the base64
program. This is something that's available in almost any Linux or Unix-based operating system (but not on Windows).
In this situation, you can quickly spin up a container using images like busybox and let it do the job.
The generic syntax for encoding a string using base64
is as follows:
echo -n solomon-secret | base64
# c29sb21vbi1zZWNyZXQ=
And the generic syntax for passing a command to a container that is not running is as follows:
docker container run <image name> <command>
To perform the base64 encoding using the busybox image, you can execute the following command:
docker container run --rm busybox sh -c "echo -n solomon-secret | base64
# c29sb21vbi1zZWNyZXQ=
What happens here is that, in a container run
command, whatever you pass after the image name gets passed to the default entry point of the image to be executed.