Using the Docker command-line¶
In this section you’ll learn to make use of docker images, created by others, by using the command line.
Docker has a very active community and most everything you can think of and is publicly available has been build into a docker image by someone already. It makes trying out new things very easy.
At the end of this section you should:
Be more familiar with most commonly used docker commands, like: run, exec, ps, rm, rmi, attach and probably some more
Know how to start and stop a container in different ways and know when to use which
Have working knowledge of docker processes
Have learned a few best practices
Know how to cleanup your docker environment
Be familiar with terms like: registry, image, container
Lazy programmers (like me) might want to clone this git repository as some files you are asked to use or create will already be provided here:
git clone https://github.com/IvoNet/docker-from-scratch.git
Commandline commands have only been tested with
bash/(z)sh (mac/linux) and
Not with PowerShell!
PowerShell will probably work just fine but some commands may need something different. This is not taken
into account in this workshop! Use at your own risk :-)
Let’s start by doing a Hello, world!. It might comfort you to know that this time honoured tradition will not be forsaken in this Hands-on Lab 😄.
Open a terminal and run
docker run ivonet/hello-world
Your terminal should show something like:
Unable to find image 'ivonet/hello-world:latest' locally latest: Pulling from ivonet/hello-world 9b18e9b68314: Already exists 3f715686f3c8: Already exists Digest: sha256:970514d359b523145d8a9cbedcef62cfa9a58560a477970dca87e07b53b5141d Status: Downloaded newer image for ivonet/hello-world:latest _ _ _ _ _ _ _ | | | | ___| | | ___ __ _____ _ __| | __| | | | |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | | | _ | __/ | | (_) | \ V V / (_) | | | | (_| |_| |_| |_|\___|_|_|\___( ) \_/\_/ \___/|_| |_|\__,_(_) |/
Because it was the first time you ran the command and the image was not yet present on your host machine it had to
pull it from the registry (hub.docker.com) before it could be run.
Docker will do this automatically for you.
Essentially the run command did this:
docker pull ivonet/hello-world docker run ivonet/hello-world
If you run the command again, only the Hello, world! message will be displayed because the image has already been pulled. It will therefore execute much faster.
docker run command uses this basic syntax:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Where all the
[OPTIONS] are to configure the
IMAGE and where
[COMMAND] [ARG ... ] are performed on the
to get more help you can look at the commandline reference or try:
docker run --help
This can be done for any docker command.
This docker container will only run long enough to print the Hello, world! message to the stdout and then stop. A container is made from the Docker image, as the GIF on the top of this page illustrates. Every time you run the same “docker run” command, a new container with its own name will be created.
In this case that is not very efficient because, except for logging, this container does not build up state and it’s behavior does not vary. Keeping the container therefore has no added value.
What would you need to change to the above run command to run the container, but not keep the container when done?
Extra Credit question¶
Try to let the
ivonet/hello-world image give another
That would be the
[COMMAND] [ARG ... ] part.
For those who don’t know the
figlet command just use:
figlet "Ok, but first Coffee"
___ _ _ _ __ _ _ / _ \| | __ | |__ _ _| |_ / _(_)_ __ ___| |_ | | | | |/ / | '_ \| | | | __| | |_| | '__/ __| __| | |_| | < _ | |_) | |_| | |_ | _| | | \__ \ |_ \___/|_|\_( ) |_.__/ \__,_|\__| |_| |_|_| |___/\__| |/ ____ __ __ / ___|___ / _|/ _| ___ ___ | | / _ \| |_| |_ / _ \/ _ \ | |__| (_) | _| _| __/ __/ \____\___/|_| |_| \___|\___|
Many images can be found in the official docker registry, also called the docker hub. You can search for an image with a specific functionality (e.g. an image with MySQL installed) by going to the docker hub website and search for it there, but you can also search through the command line:
docker search mysql
Try out this command yourself.
Sometimes you see names like
user/image-name and sometimes you only see
The images with only a single name are so called Official images.
Docker Official Images are a curated set of Docker open source and “drop-in” solution repositories.
A container can be seen as an instance of an image, but it is not exactly the same kind of instance as a programming language (e.g. java / python). In java an instance is always “running” and that is not the case with Docker. A docker container (instance) can either be running or stopped.
In the hello, world! exercise you have probably created a couple of containers as you will see in the next exercise.
Let’s find the hello-world example in the docker processes.
Many docker sub-commands have the same “command” as their equivalent in linux.
e.g. looking at running processes in linux is done with the
so if you want to see running docker processes that would translate to
docker ps [options].
With what command can you see stopped containers?
If you used the correct command you should see something like the listing below with some data like the CONTAINER ID and NAMES different. Depending on how many times you ran the Hello, world! example you might see less or more entries in the listing.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b8a8a56c1a5a ivonet/hello-world "figlet -w 1000 'Hel…" About a minute ago Exited (0) About a minute ago cocky_mestorf a028c2e6921c ivonet/hello-world "figlet -w 1000 'Hel…" 12 minutes ago Exited (0) About a minute ago beautiful_tereshkova
You see that the CONTAINER ID’s and the NAMES are all different. This shows that for each new run a new container was created.
But why would we want this? The answer in this case is that we don’t. It does illustrate nicely though that we have now created multiple containers based in 1 image and all these containers are now in the
What happens when you to start an existing
ivonet/hello-world container with
docker start <YOUR CONTAINER ID>?
You can start a stopped container with the
docker start [ID|NAME] command.
The ID can be just a unique part of the CONTAINER ID or the given NAME.
docker start b8 would already be unique in the above mentioned list of processes and
therefore enough for docker to identify the container.
Did you see the Hello, world! message again? Why (not) do you think? Do you think the message was printed? Where would that be? Can we still see the output?
Where does a container writes its output to? how can you see this log?
Stopped containers when started again will always run in daemon mode, also called detached mode.
That means that it is runs as a background process and no output is written to the active console.
It is possible though to
attach to a running process…
Foreground / Background process¶
Let’s start a foreground process.
docker run --name shell -it busybox /bin/sh
Explain what this command does with all it’s parameters (the
-it option could also have been written like this:
You should see a new prompt. This is the
/bin/sh prompt provided by the busybox container. A shell is
a long running process, so the container keeps on running. You can see that by opening
a new terminal and performing the
docker ps command.
If you want to exit (stop) this container from the provided prompt you can do this by ctrl-d or exit as that will exit the shell. For other containers running in interactive TTY mode this might be something else.
How would you
stop this process with a docker command? (from another terminal window)
If all went well you can see that the container is not running anymore in the first terminal.--- Solution ---
If you stop a container it will still exist as a stopped container unless the
--rm option was provided
run command. You should not see this container with the
docker ps command as that will only
show running processes, but should be visible with the
docker ps -a command.
Try these commands yourself.
startthe existing container called ‘shell’ again.
If all went well you only got
shell as feedback in the terminal.
You should also be able to see the container in the running processes.
The trouble is that we want to access the shell again but now it is running in detached mode.
attach to the running container called
If all went according to plan you should see the shell prompt from the container. Commands you perform at this point are performed within the container.--- Solutions ---
The next logical step is to remove a container.
Remove the container called ‘shell’
Remove the container(s) from the hello-world example
If the container is still running you might want to stop it first or force the removal.
--name a container yourself and try to run the same command again to create a new container it will
fail with a message like:
docker: Error response from daemon: Conflict. The container name "/shell" is already in use by container "5f9da09c00b8d5e3c7690a978127f5bf2fdd328fec0d2ed44e4b44df0d8fcc88". You have to remove (or rename) that container to be able to reuse that name. See 'docker run --help'.
Names of containers must be unique on the host machine. If no name is provided docker will generate a unique name itself. They can be funny sometimes.
So now you should be able to:
searchfor images in the registry
pullan image from the registry
runa basic container in the background (detached) of in the foreground
ps [-a]to view containers either running or stopped
startan existing container
attachto a detached running container
rm) a container
Running Linux commands inside a Docker container is fun, but let’s do something more useful.
Until now you have created your own long running process by providing one on the commandline to an image with no special function of its own. The whole goal of docker is to have specialized containers, with everything in it, doing a specific thing.
Let’s look at such a specialized docker container next and learn about exposing functionality.
A basic webserver¶
ivonet/nginx-exampleDocker image from the Docker hub (registry). This image uses the nginx webserver to serve a static HTML website.
Start a new container from the
ivonet/nginx-exampleimage that publishes port 80 from the container to port 3000 on your host.
Go in a browser to http://localhost:3000
If you did it right you should see a nice green message 😄
If you started the container as a foreground process you will now also see logging done by nginx to the console.
Stop the container
Start the container again and look at the message again (should work)
logswith a docker command (tail them)
Remove the container
Mapping ports between your host machine and your containers can be a bit confusing. Docker does application level isolation, which means that you have no access to the ports exposed in a container unless you explicitly make them available when creating the container.
The trick is to remember that the host port always goes on the left, and the container port always goes on the right.
Think of it from the perspective of an incoming request, which would first go through port 3000 on your host and then be forwarded to port 80 on your container.
If you want to detach from a foreground process without stopping the container you can do that by pressing ctrl+p and ctrl+q after each other.
Extra Credit question¶
Run the above server again with
docker run --name coolness -d -p 3000:80 ivonet/nginx-example
and prove that it works.
Now you have a docker container running in the background called coolness, serving the nice message.
How can you execute other commands on this container while it is running?
can you start a
/bin/shshell and execute commands?
What number (PID) does the main process have?
Many containers out there need to save data. For example if you have a MySQL docker container and you are creating a database with tables and data, it needs to be saved somewhere. The state of a container will be saved, so you can save your data inside the container. Though, this is generally a bad idea:
Your software (MySQL) and your data are now coupled (no separation of concerns)
What if you don’t need the mysql software for a while but don’t want to lose your data?
If you remove your container you lose your data too.
Upgrading your MySQL software becomes almost impossible without losing your data.
Backing up your data is much more difficult.
Good reasons not to save data in the same container als where the software runs. To save your data in Docker, you can use volumes.
Volumes are a way of sharing data either with the host machine or in a volume container.
First let’s take a look at sharing data with the host machine
Create a directory in your project directory called data
Create a file called host.txt in the data folder
ivonet/nginx-exampleimage with the following options:
as an interactive tty foreground process
mount the local data volume to the container
Show what is in the /data directory of the container
You can use the option
-v <host/path>:<container/path> to mount a data volume to a container.
Note that to mount a directory from the host machine you need to provide a fully qualified path.
As with ports the host path is on the left and the container path on the right.
While in the container create a new file called
/data/container.txt(you can create a file using the
Exit the container and see what is now in the <host>/data directory
If you do not use fully qualified paths you are actually using the volume container option.
On the web page of nginx on docker hub you get a very nice description what it can do and how you can execute commands.
ivonet/nginx-example image is derived from that image.
One of the things you can do is mount a volume where nginx is actually watching for files.
# Linux / Mac docker run --rm -d -p 3001:80 -v $(pwd)/data:/usr/share/nginx/html ivonet/nginx-example # Windows docker run --rm -d -p 3001:80 -v "%CD%\data":/usr/share/nginx/html ivonet/nginx-example # Windows Powershell docker run --rm -d -p 3001:80 -v "$((pwd).Path)\data:/usr/share/nginx/html" ivonet/nginx-example
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hello</title> </head> <body> <h1>Hello, NGINX!</h1> </body> </html>
Now you have nginx running in a docker container listening on its port 80 in the
That directory is mounted to a host machine directory and you can edit files which are picked up immediately by the container.
This feature is very handy during development. You do not need to regenerate a docker image every time you make a change, but can live
see the changes you make and when the time comes that you are happy with the result you can build a real image with the data.
Very cool stuff 😄!
Think about it. If you use an Application Server like Payara to serve your thin Jakarta EE war, but you did not install Payara natively on your laptop, how do you deploy and test? By building a docker image from it and running it. But Payara also has an auto deploy folder, so what if you could just build your war and place it in a certain host folder that has been mounted to the autodeploy directory of Payara in the container? Then you suddenly have a much faster development cycle. Read more about it on this blog post (optional).
The second way of saving data is in another special container, the so called Data Volume. Data volumes are container with no other function than storing data.
Docker will manage where the container is saved.
Usage: docker volume COMMAND Manage volumes Commands: create Create a volume inspect Display detailed information on one or more volumes ls List volumes prune Remove all unused local volumes rm Remove one or more volumes Run 'docker volume COMMAND --help' for more information on a command.
You can start the same example as before, but now with a data-volume, as follows:
docker run --rm -d -p 3001:80 -v my-data:/usr/share/nginx/html ivonet/nginx-example
Just by leaving out the path on the left side of the
-v option you are creating a data volume inside a container at /my-data.
Volumes are initialized when a container is created. If the container’s base image contains data at the specified mount point, that existing data is copied into the new volume upon volume initialization. (This does not apply when mounting a host directory).
Data volumes kan be shared and reused among containers.
Changes to a data volume are made directly.
Changes to a data volume will not be included when you update an image.
Data volumes persist even if the container itself is deleted.
docker run --rm -d -p 3001:80 -v my-data:/usr/share/nginx/html ivonet/nginx-example
Look at the volume just created
How would you list the contents of the data volume? (Brain teaser!)
Create your own data volume with the
docker volume ...command
How would you copy (
docker cp --help) the index.html (former exercise) to this volume? (Brain teaser!)
Check if you actually copied something to that volume by using another container.
You can find any kind of information about a container by
inspect -ing it. In common usage you will not often
need to use this command, but it is good to know about it.
Below are some examples performed on the Virtual Machine:- Inspect full example (show/hide)
- Inspect example with filter (show/hide)
See the Docker Inspect reference for more information.
Above you explored how to start and stop containers. Containers are instances of images.
Images can be viewed with the
docker images command.
As the images are the base of all the containers you create, you can accumulate quite a few over time.
Docker images are like onions. Not because they stink or make you cry but because they have layers. We will go into layers in the Creating Docker images section, but here is a short introduction:
For example if you compare the history of the
nginx image with the history of the
you will see something like:
$ docker history nginx:1.17.3-alpine IMAGE CREATED CREATED BY SIZE COMMENT 41c8c3458a93 11 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B <missing> 11 days ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B <missing> 11 days ago /bin/sh -c #(nop) EXPOSE 80 0B <missing> 11 days ago /bin/sh -c set -x && addgroup -g 101 -S … 15.7MB <missing> 11 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1 0B <missing> 11 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.3.5 0B <missing> 11 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.17.3 0B <missing> 11 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B <missing> 11 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 11 days ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB $ docker history ivonet/docker-from-scratch IMAGE CREATED CREATED BY SIZE COMMENT 6a77a4bebb74 29 hours ago /bin/sh -c #(nop) COPY dir:e1baa3f8487c01720… 69.7MB <missing> 11 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B <missing> 11 days ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B <missing> 11 days ago /bin/sh -c #(nop) EXPOSE 80 0B <missing> 11 days ago /bin/sh -c set -x && addgroup -g 101 -S … 15.7MB <missing> 11 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1 0B <missing> 11 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.3.5 0B <missing> 11 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.17.3 0B <missing> 11 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B <missing> 11 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 11 days ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB
You can see that the ivonet/docker-from-scratch is the same image as the nginx image,
but with an extra layer. You could say that the
Take a look at histories of some of the other images on your machine.
At this point you should be:
More comfortable with the docker cli ;-)
Publish ports (
Use (data-)volumes (
Inspect containers (
docker inspect ...)
Understand the image layer system and see its
Next you will start creating your own docker images…