loading...

Docker – The Docker run command

How to Create MySQL Users Accounts and Grant Privileges

Since we will be using the run command a lot, we should take a look at that now. You have already used the run command in its most basic form:

# new syntax
# Usage: docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker container run hello-world

# old syntax
docker run hello-world

This command tells Docker that you want to run a container based on the image described as hello-world. You may be asking yourself, did the hello-world container image get installed when I installed Docker? The answer is no. The docker run command will look at the local container image cache to see whether there is a container image that matches the description of the requested container. If there is, Docker will run the container from the cached image. If the desired container image is not found in the cache, Docker will reach out to a Docker registry to try to download the container image, storing it in the local cache in the process. Docker will then run the newly-downloaded container from the cache.

A Docker registry is just a centralized location to store and retrieve Docker images. We will talk more about registries and the Docker registry specifically later. For now, just understand that there is a local image cache and a remote image store. You saw the container not found locally process occur when we ran the hello-world container in Chapter 1, Setting up a Docker Development Environment. Here is what it looks like when Docker does not find the container image in the local cache and has to download it from the registry:

You can pre-seed the local docker cache with container images you plan to run by using the docker pull command; for example:

# new syntax
# Usage: docker image pull [OPTIONS] NAME[:TAG|@DIGEST]
docker image pull hello-world

# old syntax
docker pull hello-world

If you prefetch the container image with a pull command, when you execute the docker run command, it will find the image in the local cache and not need to download it again. 

You may have noticed in the preceding screenshot that you requested the hello-world container image and Docker unsuccessfully searched the local cache and then downloaded the hello-world:latest container image from the repository. Each container image description is made up of three parts:

  • Docker registry host name
  • Slash-separated name
  • Tag name

The first part, the registry host name, we have not seen or used yet, but it was included via a default value of the public Docker registry. Whenever you do not specify a registry host name, Docker will invisibly use the public Docker registry. This registry host name is docker.io. The contents of the Docker registry can be browsed at https://hub.docker.com/explore. This is the main public store for Docker images. It is possible to set up and use other public or private image registries, and many corporations will do just that, setting up their own private Docker image registry. We will talk a little more about that in Chapter 8, Docker and Jenkins. For now, just understand that the first part of a Docker image description is the registry host name that hosts the container image. It is worth noting that the registry host name can include a port number. This can be used for registries that are configured to serve data on a non-default port value.

The second part of the container image description is the slash-separated name. This part is like a path to, and name of, the container image. There are certain official container images that do not need to specify the path. For those images, you can simply specify the name portion of the slash-separated name. In our example, that is the hello-world part of the description.

The third part of the container image description is the tag name. This part is considered the version tag for the image, but it does not need to be made up of just numbers. The tag name can be any set of ASCII characters, including uppercase and lowercase letters, numbers, dashes, underscores, or periods. About the only restrictions on tag names are that they cannot start with a period or dash, and have to be 128 characters or fewer. The tag name is separated from the slash-separated name by a colon. This brings us back to the hello-world:latest image description we saw earlier. Like the registry host name, there is a default value for the tag name. That default value is latest. In our example, the tag name being used is the default, and it is shown in the search and download as hello-world:latest. You can see all of this in action in the following example:

We confirmed that our local image cache is empty, with the docker images command, and we then pulled the fully qualified hello-world image to prefetch it into our local cache. Then we used the same short description as we did in all of our previous hello-world examples, and Docker runs the container without downloading again, showing that the default values are used and that they match the fully-qualified values.

Okay, now that we have all of the basics of the Docker run command out of the way, let’s dig a little deeper and examine some of the optional parameters that you can use with the run command. If you look at the full run command syntax, you will see this:

# Usage: docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]

Note that the last parts of the command are [COMMAND] [ARG...]. This tells us that the container run command has an optional command parameter that can also include its own optional parameters. Docker container images are built with a default command that is executed when you run a container based on the image. For the hello-world container, the default command is /hello. For a full Ubuntu OS container, the default command is bash. Whenever you run an Ubuntu container and don’t specify a command to run in the container, the default command will be used. Don’t worry if this doesn’t make much sense yet—we will cover the default command and overriding it at runtime later in this chapter in the Back to the Docker run command section. For now, it is enough to know that when you run a container, it will execute a command that is either the default command or, if provided to the container run command, an override command to execute in the running container. One last note: when the command being executed by the running container (either default or override) terminates, the container will exit. In our examples using the hello-world container, as soon as the /hello command terminates inside the container, the hello-world container exits. In a moment, you will learn more about the difference between a running container and one that has exited.

For now, we will continue our run command discussion with one of my favorite optional parameters, the --rm parameter. A little background information is required here. As you may recall from Chapter 1, Setting up a Docker Development Environment, a Docker image is made up of layers. Whenever you run a docker container, it is really just using the locally-cached docker image (which is a stack of layers), and creating a new layer on top that is a read/write layer. All of the execution and changes that occur during the running of a container are stored in its own read/write layer.

The list container command

The indication of a running container can be shown using the following command:

# Usage: docker container ls [OPTIONS]
docker container ls

This is the list containers command, and without any additional parameters, it will list the currently-running containers. What do I mean by currently running? A container is a special process running on the system, and like other processes on the system, a container can stop or exit. However, unlike other types of processes on your system, the default behavior for a container is to leave behind its read/write layer when it stops. This is because you can restart the container if desired, keeping the state data it had when it exited. As an example, imagine you run a container that is an OS, such as Ubuntu, and in that container you install wget. After the container exits, you can restart it, and it will still have wget installed. Remember that each running container has its own read/write layer, so, if you run one Ubuntu container and install wget, then you run another Ubuntu container, it will not have wget. The read/write layers are not shared between containers. However, if you restart a container that had the wget installed, it will still be installed.

So, the difference between a running container and a stopped one is that the process is either running or it has exited, leaving behind its own read/write layer. There is a parameter to the list containers command that allows you to list all of the containers, both those running and those that have exited. As you may have guessed, it is the --all parameter, and it looks like this:

# short form of the parameter is -a
docker container ls -a
# long form is --all
docker container ls --all

# old syntax
docker ps -a

Now, let’s go back to one of my favorite optional run command parameters, the --rm parameter:

# there is no short form of the --rm parameter
docker container run --rm hello-world

This parameter instructs Docker to remove the container’s read/write layer automatically when the container exits. When you run a docker container without the --rm parameter, the container data is left behind when the container exits so that the container can be restarted again later. If, however, you include the --rm parameter when you run a container, all of the container’s read/write data is removed at the time the container exits. This parameter provides an easy clean up on exit function that you will often find very helpful. Let’s see this with a quick example using the run and container ls commands we just discussed:

First, we confirmed we had the hello-world image in our local cache. Next, we listed all of the containers on our system, both running and exited. Note the distinction between images and containers. If you are familiar with VMware, the analogy would be somewhat like a template and a VM. Next, we ran the hello-world container using the --rm parameter. The hello-world container prints its message and then immediately exits (we redirected the output to /dev/null to keep the example output short). Next, we listed the containers again, as we saw that the hello-world container’s read/write data was automatically removed when the container exited. After that, we ran the hello-world container again, but this time did not use the --rm parameter. When we listed the containers this time, we saw the indication of the (exited) container. Often you will run a container, knowing that you will never need to restart it later, and using the --rm parameter to automatically clean it up is very handy. But what if you don’t use the --rm parameter? Are you stuck with an ever-growing list of containers? Of course not. Docker has a command for that. It is the container rm command.

The remove container command

The remove container command looks something like this:

# the new syntax
# Usage: docker container rm [OPTIONS] CONTAINER [CONTAINER...]
docker container rm cd828234194a

# the old syntax
docker rm cd828234194a

The command requires a value that uniquely identifies a container; in this case, I used the full container ID for the hello-world container that we just ran. You can use the first few characters of a container’s ID, as long as it provides a unique identifier between all of the containers on your system. Another way to uniquely identify the container is by the name assigned to it. Docker will provide a unique randomly-generated name for your container when you run it. In the preceding example, the random name assigned was competent_payne. So we could have used the remove command like this:

# using the randomly generated name
docker container rm competent_payne

While the randomly-generated names provided by docker are more human-readable than the container IDs it assigns, they still may not be as relevant as you would like. This is why docker has provided an optional parameter to the run command for naming your containers. Here is an example using the --name parameter:

# using our own name
docker container run --name hi-earl hello-world

Now when we list all of the containers, we can see our container has the name hi-earl. Of course, you would probably want to use a better container name, perhaps one that describes the function performed by the container, such as db-for-earls-app.

Note: Like the container IDs, the container names must be unique on a host. You cannot have two containers (even if one has exited) that have the same name. If you will have more than one container running the same image, such as web server image, name them uniquely, for example, web01 and web02.

You can delete multiple containers at the same time by providing the unique identifier for each on the command line:

# removing more than one
docker container rm hi-earl hi-earl2

Usually, you will remove containers only after they have exited, such as the hello-world containers that we have been using. However, sometimes you will want to remove a container even if it is currently running. You can use the --force parameter to handle that situation. Here is an example of using the force parameter to remove a running container:

# removing even if it is running
docker container rm --force web-server

Here is what that would look like:

Notice that in the first container ls command, we didn’t use the --all parameter. This reminds us that the web server container is running. When we tried to remove it, we were informed that the container is still running and would not be removed. This is a good safeguard to help prevent the removal of running containers. Next, we used the force command, and the running container was removed without any warning. Finally, we did another container ls command, including the --all parameter to show that the read/write data for our container was actually removed this time.

If you have set up Docker command completion, you can type in the command up to where you need to enter the unique identifier for the container(s) and then use the Tab key to get a list of containers, tabbing to the one you want to delete. Once you’ve highlighted the container to delete, use the space or Enter key to select it. You can hit Tab again to select another container to delete more than one at a time. Once you have all the containers selected, press Enter to execute the command. Remember that you will only see stopped containers when you tab for the rm command unless you include the force parameter, rm -f.

Sometimes, you may want to remove all of the containers on your system, running or not. There is a useful way to handle that situation. You can combine the container ls command and the container remove command to get the job done. You will be using a new parameter on the container ls command to accomplish this—the --quiet parameter. This command instructs Docker to only return the container IDs instead of the full list with a header. Here is the command:

# list just the container IDs
docker container ls --all --quiet

Now we can feed the values returned by the container ls command as input parameters to the container remove command. It will look like this:

# using full parameter names
docker container rm --force $(docker container ls --all --quiet)
# using short parameter names
docker container rm -f $(docker container ls -aq)

# using the old syntax
docker rm -f $(docker ps -aq)

This will remove all of the containers both running and exited from your system, so be careful!

You will probably use this shortcut often, so creating a system alias for it is pretty handy.
You can add something like the following to your ~/.bash_profile or ~/zshrc file: alias RMAC='docker container rm --force $(docker container ls --all --quiet)'.

Many containers are designed to run and exit immediately, such as the hello-world example we’ve used several times already. Other container’s images are created so that, when you run a container using it, the container will continue running, providing some ongoing useful function, such as serving web pages. When you run a container that persists, it will hold onto the foreground process until it exits, attaching to the processes: standard input, standard output, and standard error. This is okay for some testing and development use cases, but normally, this would not be desired for a production container. Instead, it would be better to have the container run as a background process, giving you back control of your terminal session once it launches. Of course, there is a parameter for that. It is the --detach parameter. Here is what using that parameter looks like:

# using the full form of the parameter
docker container run --detach --name web-server --rm nginx
# using the short form of the parameter
docker container run -d --name web-server --rm nginx

Using this parameter detaches the process from the foreground session and returns control to you as soon as the container has started. Your next question is probably, how do I stop a detached container? Well, I am glad you asked. You use the container stop command.

The stop container command

The stop command is easy to use. Here are the syntax and an example of the command:

# Usage: docker container stop [OPTIONS] CONTAINER [CONTAINER...]
docker container stop web-server

In our case, we used the --rm parameter when running the container, so as soon as the container is stopped, the read/write layer will be automatically deleted. Like many of the Docker commands, you can provide more than one unique container identifier as parameters to stop more than one container with a single command.

Now you might be wondering if I use the --detach parameter, how do I see what is happening with the container? There are several ways you can get information from, and about, the container. Let’s take a look at some of them before we continue with our run parameter exploration.

The container logs command

When you run a container in the foreground, all of the output the container sends to standard output and standard error is displayed in the console for the session that ran the container. However, when you use the --detach parameter, control of the session is returned as soon as the container starts so you don’t see the data sent to stdout and stderr. If you want to see that data, you use the container logs command. That command looks like this:

# the long form of the command
# Usage: docker container logs [OPTIONS] CONTAINER
docker container logs --follow --timestamps web-server
# the short form of the command
docker container logs -f -t web-server

# get just the last 5 lines (there is no short form for the "--tail" parameter)
docker container logs --tail 5 web-server

# the old syntax
docker logs web-server

The --details, --follow, --timestamps, and --tail parameters are all optional, but I have included them here for reference. When you use the container logs command with no optional parameters, it will just dump all of the contents of the container’s logs to the console. You can use the --tail parameter with a number to dump just the last number of lines. You can combine the parameters (except for --tail and --follow) to get the results you want. The --follow parameter is like using a tail -f command when viewing logs that are being continually written to, and will display each line as it is written to the log. You use Ctrl C to exit the log being followed. The --timestamps parameter is great for evaluating the frequency at which lines have been written to the container’s logs.

The container top command

You may not always want to simply view the logs of a container; sometimes you want to know what processes are running inside a container. That’s where the container top command comes in. Ideally, each container is running a single process, but the world is not always ideal, so you can use a command such as this to view all the processes running in the targeted container:

# using the new syntax
# Usage: docker container top CONTAINER [ps OPTIONS]
docker container top web-server

# using the old syntax
docker top web-server

As you might expect, the container top command is only used for viewing the processes of a single container at a time.

The container inspect command

When you run a container, there is a lot of metadata that gets associated with the container. There are many times that you will want to review that metadata. The command for doing that is:

# using the new syntax
# Usage: docker container inspect [OPTIONS] CONTAINER [CONTAINER...]
docker container inspect web-server

# using the old syntax
docker inspect web-server

As mentioned, this command returns a lot of data. You may only be interested in a subset of the metadata. You can use the --format parameter to narrow the data returned. Check out these examples:

  • Getting some State data:
# if you want to see the state of a container you can use this command
docker container inspect --format '{{json .State}}' web-server1 | jq

# if you want to narrow the state data to just when the container started, use this command
docker container inspect --format '{{json .State}}' web-server1 | jq '.StartedAt'
  • Getting some NetworkSettings data:
# if you are interested in the container's network settings, use this command
docker container inspect --format '{{json .NetworkSettings}}' web-server1 | jq

# or maybe you just want to see the ports used by the container, here is a command for that
docker container inspect --format '{{json .NetworkSettings}}' web-server1 | jq '.Ports'

# maybe you just want the IP address used by the container, this is the command you could use.
docker container inspect -f '{{json .NetworkSettings}}' web-server1 | jq '.IPAddress'
  • Getting data for more than one container with a single command:
# maybe you want the IP Addresses for a couple containers
docker container inspect -f '{{json .NetworkSettings}}' web-server1 web-server2 | jq '.IPAddress'

# since the output for each container is a single line, this one can be done without using jq
docker container inspect -f '{{ .NetworkSettings.IPAddress }}' web-server1 web-server2 web-server3

Most of these examples use the json processor, jq. If you haven’t already installed it on your system, now is a good time to do so. Here are the commands to install jq on each of the OSes we’ve used in this book:

# install jq on Mac OS
brew install jq

# install jq on ubuntu
sudo apt-get install jq

# install jq on RHEL/CentOS
yum install -y epel-release
yum install -y jq

# install jq on Windows using Chocolatey NuGet package manager
chocolatey install jq

The --format parameter of the inspect command uses go templates. You can find more information about them on the Docker document pages for formatting output: https://docs.docker.com/config/formatting.

The container stats command

Another very useful Docker command is the stats command. It provides live, continually-updated usage statistics for one or more running containers. It is a bit like using the Linux top command. You can run the command with no parameters to view the stats for all running containers, or you can provide one or more unique container identifiers to view the stats for one or more container’s specific containers. Here are some examples of using the command:

# using the new syntax, view the stats for all running containers
# Usage: docker container stats [OPTIONS] [CONTAINER...]
docker container stats

# view the stats for just two web server containers
docker container stats web-server1 web-server2

# using the old syntax, view stats for all running containers
docker stats

When you have seen enough stats, you use CtrlC to exit the view.

Getting back to run command parameters, next, we’ll discuss two parameters for the run command that are usually used together. Sometimes you run a container, and you want to have an interactive session with it. For example, you may run a container that executes some application inside a more or less full OS, such as Ubuntu, and you want to have access inside that container to change the configuration or debug some issue, similar to using SSH to connect to a server. As with most things Docker, there is more than one way to accomplish this. One common method is to use two optional parameters for the run command: --interactive and --tty. Let’s take a look at how that works now. You have already seen how we can use the --detach parameter startup disconnected from the container we are running:

# running detached
docker container run --detach --name web-server1 nginx

When we run this command to start up our nginx web server and browse to http://localhost, we find that it is not serving the welcome page we expect. So we decide to do some debugging, and, instead of detaching from our container, we decide to run it interactively using the two --interactive and --tty parameters. Now, since this is a nginx container, it has a default command that is executed when the container starts. That command is nginx -g 'daemon off;'. Since that is the default command, it won’t do us any good to interact with the container. So we are going to override the default command by providing one as a parameter to our run command. It will look something like this:

# using the long form of the parameters
docker container run --interactive --tty --name web-server2 nginx bash

# using the short form of the parameters (joined as one), which is much more common usage
docker container run -it --name web-server2 nginx bash

This command will run the container as before, but instead of executing the default command, it will execute the bash command. It will also open a terminal session with the container that we can interact with. As needed, we can execute commands inside of the container as the root user. We can view folders and files, we can edit configuration settings, we can install packages, and so on. We can even run the image’s default command to see whether we have resolved any issues. Here is a somewhat contrived example:

You may have noticed the -p 80:80 parameter. That is the short form of the publish parameter, which we will discuss shortly in the Back to the Docker run command section. Using the container ls command, you can see the differences between running the container using the default command versus running the container using an override command:

Web-server run used the default CMD, and web-server2 used the override CMD bash. This is a contrived example to help you understand these concepts. A real-world example might be when you want to interactively connect with an OS-based container, such as Ubuntu. You may recall that at the beginning of Chapter 1, Setting up a Docker Development Environment, it said that the default command run in an Ubuntu container is bash. Since that is the case, you don’t have to supply a command to override the default. You can use a run command like this:

# running interactively with default CMD
docker container run -it --name earls-dev ubuntu

With this container run command, you are connected to an interactive terminal session of your running Ubuntu container. You can do pretty much anything you would normally do when ssh-ed into an Ubuntu server. You can install software with apt-get, you can view running processes, you can execute a top command, and so on. That might look like this:

There are a couple of other container commands that help you interact with a container that is already running and is detached. Let’s take a quick look at these commands now.

The container attach command

Suppose you have a running container. It is currently detached from your terminal session. You can use the container attach command to bring that container’s executing process to be the foreground process of your terminal session. Let’s use the web-server example we used earlier:

# run a container detached
docker container run --detach -it --name web-server1 -p 80:80 nginx

# show that the container is running
docker container ps

# attach to the container
# Usage: docker container attach [OPTIONS] CONTAINER
docker container attach web-server1

# issue a Ctrl + PQ keystroke to detach (except for Docker on Mac, see below for special Mac instructions)

# again, show that the container is running detached.
docker container ps

When you attach to the running container, its executing command becomes the foreground process for your terminal session. To detach from the container, you need to issue a Ctrl + PQ keystroke. If you issue a CtrlC keystroke, the container’s executing process will receive a sig-term signal and will terminate, which in turn will exit the container. This is usually not desired. So remember to detach by using a Ctrl + PQ keystroke. 

However, there is a known issue on macOS: for Docker on Mac, the Ctrl + PQ keystroke does not work, and unless you use another parameter, the --sig-proxy=false parameter, on the attach command, you will not be able to detach from the container without terminating it with a Ctrl + C keystroke:

# when you are using Docker for Mac, remember to always add the "--sig-proxy=false" parameter
docker attach --sig-proxy=false web-server1

When you provide the --sig-proxy=false parameter to the attach command, you can issue a Ctrl C keystroke to the attached container and it will detach without sending the sig-term signal to the container process, thus keeping the container running, once again detached from your terminal session:

The container exec command

Sometimes, when you have a container running detached, you might want to get access to it, but don’t want to attach to the executing command. You can accomplish this by using the container exec command. This command allows you to execute another command in the running container, without attaching to or interfering with the already-running command. This command is often used to create an interactive session with an already-running container or to execute a single command within the container. The command looks like this:

# start an nginx container detached
docker container run --detach --name web-server1 -p 80:80 nginx

# see that the container is currently running
docker container ls

# execute other commands in the running container
# Usage: docker container exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker container exec -it web-server1 bash
docker container exec web-server1 cat /etc/debian_version

# confirm that the container is still running 
docker container ls

When the exec command completes, you exit the bash shell, or the file contents have been displaced, then it exits back to the terminal session leaving the container running detached:

Let’s take a look at another Docker command before we continue our discussion of the many optional container run parameters. 

The container commit command

It is important to know that when you are attached to a running container and make changes to it, such as installing new packages, or changing configuration files, that those changes only apply to that running container. If, for example, you use an Ubuntu image to run a container and then install curl into that container, the change does not apply back to the image you ran the container from, in this example, Ubuntu. If you were to start another container from the same Ubuntu image, you would need to install curl again. However, if you want to have the changes you make inside a running container persist and be available when you run new containers, you can use the container commit command. The container commit command allows you to save the current read/write layer of a container along with the layers of the original image, creating a brand new image. When you run containers using the new image, it will include the changes you made and saved with the container commit command. Here is what the container commit command looks like:

# Usage: docker container commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
docker container commit ubuntu new-ubuntu

And here is an example of using the container commit command to install curl to a running container, and then creating a new container that includes the installed curl command:

With this example, I can now run new containers from the ubuntu-curl image, and all of them will have the curl command already installed.

Back to the Docker run command

Now, let’s return to our discussion of the container run command. Earlier, you saw an example of using the run command with the --publish parameter. Using the optional publish parameter allows you to specify what ports will be opened related to the run container. The --publish parameter includes pairs of port numbers separated by a colon. For example:

# create an nginx web-server that redirects host traffic from port 8080 to port 80 in the container
docker container run --detach --name web-server1 --publish 8080:80 nginx

The first port number is associated with the host running the container. In the nginx example, 8080 is exposed on the host; in our case that would be http://localhost:8080. The second port number is the port that is open on the running container. In this case, it would be 80. Speaking out the description of the --publish 8080:80 parameter, you would say something like, the traffic sent to port 8080 on the host is redirected to port 80 on the running container:

It is an important distinction to make between the host ports and the container ports. I can run several containers on the same system that all expose port 80, but only one container can have traffic from each port on the host. Look at the following examples to better understand:

# all of these can be running at the same time
docker container run --detach --name web-server1 --publish 80:80 nginx
docker container run --detach --name web-server2 --publish 8000:80 nginx
docker container run --detach --name web-server3 --publish 8080:80 nginx
docker container run --detach --name web-server4 --publish 8888:80 nginx

# however if you tried to run this one too, it would fail to run 
# because the host already has port 80 assigned to web-server1
docker container run --detach --name web-server5 --publish 80:80 nginx

Know that this is a limitation of networking in general, not a limitation of Docker or containers. Here we can see these commands and their output. Notice the ports and names, and how the use of a port already used as an endpoint fails:

That was a lot of data on various options parameters for the container run command. It’s not all the options parameters, but it should be enough to get you off to a great start. If you want to learn more about the optional parameters we explored, or find out about the ones we didn’t cover here, be sure to visit the docker documents page for the container run command, which can be found at https://docs.docker.com/engine/reference/run/.

Comments are closed.

loading...