Container Manipulation Basics
In the previous sections, you've learned about the building blocks of Docker and have also run a container using the
docker run command. In this section, you'll be learning about container manipulation in a lot more detail. Container manipulation is one of the most common task you'll be performing every single day so having a proper understanding of the various commands is crucial.
Keep in mind though, this is not an exhaustive list of all the commands you can execute on Docker. I'll be talking only about the most common ones. Anytime you want to learn more about the available commands, just visit the official reference for the Docker command-line.
Previously you've used
docker run to create and start a container using the
hello-world image. The generic syntax for this command is as follows:
docker run <image name>
Although this is a perfectly valid command, there is a better way of dispatching commands to the
docker daemon. Prior to version
1.13, Docker had only the previously mentioned command syntax. Later on, the command-line was restructured to have the following syntax:
docker <object-type> <command> <options>
In this syntax:
object-typeindicates the type of Docker object you'll be manipulating. This can be a
commandindicates the task to be carried out by the daemon i.e.
optionscan be any valid parameter that can override the default behavior of the command i.e. the
--publishoption for port mapping.
Now, following this syntax, the
run command can be written as follows:
docker container run <image name>
image name can be of any image from an online registry or your local system. As an example, you can try to run a container using the fhsinchy/hello-dock image. This image contains a simple Vue.js application that runs on port 80 inside the container. To run a container using this image, execute following command on your terminal:
docker container run --publish 8080:80 fhsinchy/hello-dock # /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: Getting the checksum of /etc/nginx/conf.d/default.conf # 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf # /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh # /docker-entrypoint.sh: Configuration complete; ready for start up
The command is pretty self-explanatory. The only portion that may require some explanation is the
--publish 8080:80 portion which will be explained in the next sub-section.
Containers are isolated environments. Your host system doesn't know anything about what's going on inside a container. Hence, applications running inside a container remains inaccessible from the outside.
To allow access from outside of a container, you must publish the appropriate port inside the container to a port on your local network. The common syntax for the
-p option is as follows:
--publish <host port>:<container port>
When you wrote
--publish 8080:80 in the previous sub-section, it meant any request sent to port 8080 of your host system will be forwarded to port 80 inside the container.
Now to access the application on your browser, visit
The container can be stopped by simply hitting
ctrl + c key combination while the terminal window is in focus or closing off the terminal window completely.
Another very popular option of the
run command is the
-d option. In the example above, in order for the container to keep running, you had to keep the terminal window open. Closing the terminal window also stopped the running container.
This is because by default containers run in the foreground and attach themselves to the terminal like any other normal program invoked from the terminal. In order to override this behavior and keep a container running in background, you can include the
--detach option to the
run command as follows:
docker container run --detach --publish 8080:80 fhsinchy/hello-dock # 9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc
Unlike the previous example, you won't get a wall of text thrown at you this time. Instead what you'll get is the ID of the newly created container.
The order of the options you provide doesn't really matter. If you put the
--publish option before the
--detach option, it'll work just the same. One thing that you have to keep in mind in case of the
run command is that the image name must come last. If you put anything after the image name then that'll be passed as an argument to the container entry-point (explained in the Executing Commands Inside a Container sub-section) and may result in unexpected situations.
container ls command can be used to list out containers that are currently running. To do so execute following command:
docker container ls # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 9f21cb777058 fhsinchy/hello-dock "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp gifted_sammet
As you can see in the output, a container named
gifted_sammet is running. It was created
5 seconds ago and the status is the status is
Up 5 seconds, which indicates that the container is running fine since it's creation.
CONTAINER ID is
9f21cb777058 which is the first 12 characters of the full container ID. The full container ID is
9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc which is 64 characters long. This full container ID was printed as the output of the
docker container run command in the previous section.
Listed under the
PORTS column, port 8080 from your local network is pointing towards port 80 inside the container. The name
gifted_sammet is generated by Docker and can be something completely different in your computer.
container ls command only lists the containers that are currently running on your system. In order to list out the containers that has run in the past you can use the
docker container ls --all # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 9f21cb777058 fhsinchy/hello-dock "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp gifted_sammet # 6cf52771dde1 fhsinchy/hello-dock "/docker-entrypoint.…" 3 minutes ago Exited (0) 3 minutes ago reverent_torvalds # 128ec8ceab71 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago exciting_chebyshev
As you can see the second container in the list
reverent_torvalds was created earlier and has exited with the status code 0, which indicates that no error was produced during the runtime of the container.
Naming or Renaming Containers
By default, every container has two identifiers. They are as follows:
CONTAINER ID- a random 64 characters long string.
NAME- combination of two random words, joined with an underscore.
Referring to a container based on these two random identifier is kind of inconvenient. It would be great if the containers could be referred to using a name defined by you.
Naming a container can be achieved using the
--name option. To run another container using the
fhsinchy/hello-dock image with the name
hello-dock-container you can execute the following command:
docker container run --detach --publish 8888:80 --name hello-dock-container fhsinchy/hello-dock # b1db06e400c4c5e81a93a64d30acc1bf821bed63af36cab5cdb95d25e114f5fb
The 8080 port on local network is occupied by the
gifted_sammet container (the container created in the previous sub-section), that's why you'll have to use a different port number i.e. 8888. Now to verify, run the
container ls command:
docker container ls # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # b1db06e400c4 fhsinchy/hello-dock "/docker-entrypoint.…" 28 seconds ago Up 26 seconds 0.0.0.0:8888->80/tcp hello-dock-container # 9f21cb777058 fhsinchy/hello-dock "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 0.0.0.0:8080->80/tcp gifted_sammet
As you can see, a new container with the name of
hello-dock-container has been started.
You can even rename old containers using the
container rename command. Syntax for the command is as follows:
docker container rename <container identifier> <new name>
To rename the
gifted_sammet container to
hello-dock-container-2 execute following command:
docker container rename gifted_sammet hello-dock-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 running state and stopped state.
Stopping or Killing a Running Container
Containers running in the foreground can be stopped by simply closing the terminal window or hitting
ctrl + c key combination. Containers running in the background, however, can not be stopped in the same way.
There are two commands that deal with this task. The first one is the
container stop command. Generic syntax for the command is as follows:
docker container stop <container identifier>
container identifier can either be the id or the name of the container. I hope that you remember the container you started in the previous section. It's still running in the background. Get the identifier for that container using
docker container ls , I'll be using
hello-dock-container container for this demo. Now execute following command to stop the container:
docker container stop hello-dock-container # hello-dock-container
If you use the name as 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
docker container kill hello-dock-container-2 # hello-dock-container-2
When I say restart I mean two scenarios specifically. They are as follows:
- Restarting a container that has been previously stopped or killed.
- Rebooting a running container.
As you've already learned from a previous sub-section, stopped containers remain in your system. If you want you can restart them. The
container start command can be used to start any stopped or killed container. The syntax of the command is as follows:
docker container start <container identifier>
You can get the list of all containers by executing the
container ls --all command. Then look for the containers with
docker container ls --all # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # b1db06e400c4 fhsinchy/hello-dock "/docker-entrypoint.…" 3 minutes ago Exited (0) 47 seconds ago hello-dock-container # 9f21cb777058 fhsinchy/hello-dock "/docker-entrypoint.…" 7 minutes ago Exited (137) 17 seconds ago hello-dock-container-2 # 6cf52771dde1 fhsinchy/hello-dock "/docker-entrypoint.…" 7 minutes ago Exited (0) 7 minutes ago reverent_torvalds # 128ec8ceab71 hello-world "/hello" 9 minutes ago Exited (0) 9 minutes ago exciting_chebyshev
Now to restart the
hello-dock-container container, you may execute the following command:
docker container start hello-dock-container # hello-dock-container
Now you can ensure that the container is running by looking at the list of running containers using the
container ls command. The
container start command starts any container in detached mode by default and retains any port configurations made previously. So if you visit
http://127.0.0.1:8080 now, you should be able to access the
hello-dock application just like before.
Now, in scenarios where you would like to reboot a running container you may use the
container restart command. The
container restart command follows the exact syntax as the
container start command.
docker container restart hello-dock-container-2 # hello-dock-container-2
Main difference between the two commands is that the
container restart command attempts to stop the target container and then starts it back whereas the start command just starts a already stopped container.
In case of a stopped container, both commands are exactly same but in case of a running container, you must use the
container restart command.
Creating Containers Without Running
So far in this section, you've started containers using the
container run command which is in reality a combination of two separate commands. These commands are as follows:
container createcommand creates a container from a given image.
container startcommand starts a container that has been already created.
Now, to perform the demonstration shown in the Running Containers section using these two commands, you can do something like the following:
docker container create --publish 8080:80 fhsinchy/hello-dock # 2e7ef5098bab92f4536eb9a372d9b99ed852a9a816c341127399f51a6d053856 docker container ls --all # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 2e7ef5098bab fhsinchy/hello-dock "/docker-entrypoint.…" 30 seconds ago Created hello-dock
Evident by the output of
container ls --all command, a container with the name of
hello-dock has been created using the
fhsinchy/hello-dock image. The
STATUS of the container is
Created at the moment and given it's not running, it won't be listed without the use of the
--all option. Once the container has been created, it can be started using the
container start command.
docker container start hello-dock # hello-dock docker container ls # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 2e7ef5098bab fhsinchy/hello-dock "/docker-entrypoint.…" About a minute ago Up 29 seconds 0.0.0.0:8080->80/tcp hello-dock
STATUS has changed from
Up 29 seconds which indicates that the container is now in running state. The port configuration has also showed up in the
PORTS column which was previously empty.
Although you can get away with the
container run command for majority of the scenarios, there will be some situations later on in the article that requires the usage of this
container create command.
Removing Dangling Containers
As you've already seen, containers that have been stopped or killed remain in the system. These dangling containers can take up space or can conflict with newer containers.
In order to remove a stopped container you can use the
container rm command. The generic syntax is as follows:
docker container rm <container identifier>
To find out which containers are not running, use the
container ls --all command and look for containers with
docker container ls --all # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # b1db06e400c4 fhsinchy/hello-dock "/docker-entrypoint.…" 6 minutes ago Up About a minute 0.0.0.0:8888->80/tcp hello-dock-container # 9f21cb777058 fhsinchy/hello-dock "/docker-entrypoint.…" 10 minutes ago Up About a minute 0.0.0.0:8080->80/tcp hello-dock-container-2 # 6cf52771dde1 fhsinchy/hello-dock "/docker-entrypoint.…" 10 minutes ago Exited (0) 10 minutes ago reverent_torvalds # 128ec8ceab71 hello-world "/hello" 12 minutes ago Exited (0) 12 minutes ago exciting_chebyshev
As can be seen in the output, container with ID
128ec8ceab71 is not running. To remove the
6cf52771dde1 you can execute the following command:
docker container rm 6cf52771dde1 # 6cf52771dde1
You can check if the container was deleted or not by using the
container ls command. You can also remove multiple containers at once by passing their identifiers one after another separated by spaces.
Or, instead of removing individual containers, if you want to remove all dangling containers at one go, you can use the
container prune command.
docker container prune
Docker will ask for confirmation. You can use the
-f option to skip this confirmation step. Once done, the
container prune command will show the amount of reclaimed space.
You can check the container list using the
container ls --all command to make sure that the dangling containers have been removed:
docker container ls --all # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # b1db06e400c4 fhsinchy/hello-dock "/docker-entrypoint.…" 8 minutes ago Up 3 minutes 0.0.0.0:8888->80/tcp hello-dock-container # 9f21cb777058 fhsinchy/hello-dock "/docker-entrypoint.…" 12 minutes ago Up 3 minutes 0.0.0.0:8080->80/tcp hello-dock-container-2
If you are following the article exactly as written so far, you should only see the
hello-dock-container-2 in the list. I would suggest stopping and removing both containers before going to the next section.
There is also the
--rm option for the
container run and
container start commands which indicates that you want the containers removed as soon as they're stopped. To start another
hello-dock container with the
--rm option, execute the following command:
docker container run --rm --detach --publish 8888:80 --name hello-dock-volatile fhsinchy/hello-dock # 0d74e14091dc6262732bee226d95702c21894678efb4043663f7911c53fb79f3
You can use the
container ls command to verify that the container is running:
docker container ls # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 0d74e14091dc fhsinchy/hello-dock "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:8888->80/tcp hello-dock-volatile
Now if stop the container and then check again with the
container ls --all command:
docker container stop hello-dock-volatile # hello-dock-volatile docker container ls --all # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
The container has been removed automatically. From now on I'll use the
--rm option for most of the containers. I'll explicitly mention where it's not needed.
Running Containers in Interactive Mode
Well, all images are not that simple. Images can encapsulate an entire Linux distribution inside them. Popular distributions such as Ubuntu, Fedora, Debian all have official Docker images available in the hub. Programming languages such as python, php, go or run-times like node, deno all have their official images.
These images do not just run some pre-configured program. These are instead configured to run a shell by default. In case of the operating system images it can be something like
bash and in case of the programming languages or run-times, it is usually their default language shell.
As you may have already learned from your previous experiences with computers, shells are interactive programs. Images configured to run such a program are interactive images. These images require a special
-it option to be passed in the
container run command.
As an example, if you run a container using the
ubuntu image by executing
docker container run ubuntu you'll see nothing happens. But if you execute the same command with
-it option, you should land directly on bash inside the Ubuntu container.
docker container run --rm -it ubuntu # root@dbb1f56b9563:/# cat /etc/os-release # NAME="Ubuntu" # VERSION="20.04.1 LTS (Focal Fossa)" # ID=ubuntu # ID_LIKE=debian # PRETTY_NAME="Ubuntu 20.04.1 LTS" # VERSION_ID="20.04" # 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" # VERSION_CODENAME=focal # UBUNTU_CODENAME=focal
As you can see from the output of the
cat /etc/os-release command, I am indeed interacting with the bash running inside the Ubuntu container.
-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.
--interactiveoption connects you to the input stream of the container, so that you can send inputs to bash.
--ttyoption 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 # Welcome to Node.js v15.0.0. # Type ".help" for more information. # > ['farhan', 'hasin', 'chowdhury'].map(name => name.toUpperCase()) # [ 'FARHAN', 'HASIN', 'CHOWDHURY' ]
-it you can instead be more verbose by writing
--interactive --tty separately.
Executing Commands Inside a Container
In the Hello World in Docker section of this article, you've seen me executing a command inside an Alpine Linux container. It went something like as follows:
docker run alpine uname -a # Linux f08dbbe9199b 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 Linux
In this command, I've executed the
uname -a command inside an Alpine Linux container. Scenarios like this where all you want is to execute a certain command inside a certain container is pretty common.
Assume that you want encode a string using the
base64 program which is something 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 my-secret | base64 # bXktc2VjcmV0
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 following command:
docker container run --rm busybox sh -c "echo -n my-secret | base64" # bXktc2VjcmV0
What happens here is, in a
container run command, whatever you pass after the image name gets passed to the default entry-point of the image. An entrypoint is like a gateway to the image. Most of the images except the executable images (explained in the Working With Executable Images sub-section), uses shell or
sh as the default entry-point. So any valid shell command can be passed to them as arguments.
Working With Executable Images
In the previous section, I briefly mentioned executable images. These images are designed in a way to behave like executable programs.
Take for example my rmbyext project. This is a simple python script capable of recursively deleting files of given extensions. To learn more about the project, you can checkout the repository:
If you have both git and python installed, you can install this script by executing the following command:
pip install git+https://github.com/fhsinchy/rmbyext.git#egg=rmbyext
Assuming python has been set-up properly on your system, the script should be available anywhere through the terminal. The generic syntax for using this script is as follows:
rmbyext <file extension>
To test it out, open up your terminal inside an empty directory and create some files in it with different extensions. You can use the
touch command to do so. Now, I have a directory on my computer with following files:
touch a.pdf b.pdf c.txt d.pdf e.txt ls # a.pdf b.pdf c.txt d.pdf e.txt
To delete all the
rmbyext pdf # Removing: PDF # b.pdf # a.pdf # d.pdf
An executable image for this program should be able to take extensions of files as arguments and delete them just like the
rmbyext program did.
The fhsinchy/rmbyext image behaves in a similar manner. This image contains a copy of the
rmbyext script and is configured to run the script on a directory
/zone inside the container.
Now the problem is that containers are isolated from your local system, hence the
rmbyext program running inside the container doesn't have any access to your local file system. So, if somehow you can map the local directory containing the
/zone directory inside the container, the files should be accessible to the container.
One way to grant a container direct access to your local file system is by using bind mounts. A bind mount lets you form a two way data binding between the content of a local file system directory (source) and another directory inside a container (destination). This way any changes made in the destination directory will take effect on the source directory and vise versa.
Let's see a bind mount in action. To delete files using this image instead of the program itself, you can execute the following command:
docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf # Removing: PDF # b.pdf # a.pdf # d.pdf
As you may have already guessed by seeing the
-v $(pwd):/zone part in the command,
--volume option is used for creating a bind mount for a container. This option can take three fields separated by colons (
:). The generic syntax for the option is as follows:
--volume <local file system directory absolute path>:<container file system directory absolute path>:<read write access>
The third field is optional but you must pass the absolute path of your local directory and the absolute path of the directory inside the container. The source directory in my case is
/home/fhsinchy/the-zone and given my terminal is opened inside the directory,
$(pwd) will be replaced with
/home/fhsinchy/the-zone which contains the previously mentioned
.txt files. You can learn more about command substitution if you want to.
-v option is valid for the
container run as well as the
container create commands. Volumes will be explored in greater detail in the upcoming sections so don't worry if you didn't understand it very well here.
The difference between a regular image and an executable one is that the entry-point for an executable image is set to a custom program instead of
sh, in this case the
rmbyext program and as you've learned in the previous sub-section, anything you write after the image name in a
container run command gets passed to the entry-point of the image.
So in the end the
docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf command translates to
rmbyext pdf inside the container. Executable images are not that common in the wild but can be very useful in certain cases, hence learning about them is important.