Using Docker
Objectives
- Use an existing Docker image
- Get comfortable using both Docker desktop and from the command-line
- Use a Dockerfile to build your own image
About Docker
Docker lets you run a Linux development environment of your choice. Under the hood, Docker Desktop launches a virtual machine (guest) on your computer that runs alongside the host operating system installed on your computer. The guest VM runs a Linux OS. Docker also installs application software. You can use that software to build a Docker image and launch a Linux container in the guest VM from the image.
Using Docker, we can all run the same development environment using the Docker configuration file (Dockerfile).
The docker command. Once you have installed Docker Desktop, the best way to manage your containers is to run the docker command in a terminal window on your host computer. There is extensive documentation on the docker command. It is a "swiss army knife" with many arguments and multiple ways to do the same thing.
Understanding Docker
Docker can be confusing. Apart from all the virtual magic, the basic mental model is hard to get straight from all the words about it on the web. It is important to be precise with terminology.
The key thing to understand is that a container has a bag of files (a file tree) and zero or more active processes. The file tree is a customized (layered) view of a Linux file system. The container's file tree is seeded with a logical copy of a file tree in the container's initial image. But the file tree also includes any files created by processes running within the container. Any process that runs within the container sees the container's file tree, and may modify it. You can run many containers from the same image, and they each have their own file tree. If a process modifies the file tree in one container, those changes are not visible from other containers.
It is easy to create a new container, even by accident. You can
use docker run
to run a command (process) within a
container, say, bash or another command shell. You use
the -it
flags to run the container/process
in interactive mode, so that you can type commands to it and
see the output directly in your terminal window. docker run
always creates a new container, so any files created by a
previous run are no longer visible to the new process.
If your process in the container exits (e.g., you exit your shell), then the container becomes dormant and you return to your host shell. But the container's bag of files remains, and you can use docker start to launch another process (e.g., another shell) within the same container, where the files are visible. If you want to reclaim the disk space, you can delete the container's files with the docker rm command. If you are finished and you want to delete an image, use the command docker image prune after deleting (with docker rm) any containers created from the image.
To understand Docker, it is important to distinguish in your mind
three things: the image, the container's process (or processes), and
the container's bag of files (its file layers or file tree or file
system). There is also a Dockerfile
, which is a text
file of commands to configure the image. That's four kinds of
objects, each with its own lifecycle, and with dependency
relationships among them.
Using a Docker Image from Docker Hub
- Make sure Docker Desktop is running
- Type the following command in your terminal: docker run -dp 80:80 docker/getting-started
- Open your browser to http://localhost
- Read the page, follow instructions, and ask questions.
You pulled the
image Getting Started and then ran a container
built/based on that image. Check out the image's web page in Docker
Hub. Then, look at its Dockerfile
. I don't expect you
to understand what's going on; just want to reveal a bit about what is
behind the curtain.
- Run docker ps
- In the Docker Desktop, go to the container and view the logs.
- Stop the container and delete it.
- Run docker run --name docker_start -dp 80:80 docker/getting-started
What is different?
- Run docker ps
What does
docker ps
do?
Note that
- if you create a new container from
the
docker/getting-started
image again (as above), it will not pull the image again because you already have it. (If you didn't delete it.) - if you didn't use the
-dp 80:80
the web server in the container would not be bound to a port on the host machine, so you couldn't access it from your browser.
Now let's take a look inside the container:
- In Docker Desktop, click on your container and use the command-line interface (CLI). This opens an interactive shell into the container.
- Since you can tell it's a web application of some sort (since you looked at it in your browser), search for an index file: find / -name "index.*"
Let's look at some documentation for an image. On DockerHub, search for python
and look at its image. Skim through the documentation.
Discussion: What is the difference between a container and an image?
Continue on in the tutorial in the browser.
Maintaining your container
Installing software. While running in your
container, you may find that various tools are missing. You can use
the apt-get
command to install them. This is true, in
general, but note that if an image uses "Alpine-Linux", that is a
super stripped down version of Linux (requiring only around 5MB of
space) and you may not be able to use things like apt or bash.
Getting another shell in the same container.
Instead of using docker run (which starts a new container),
use docker exec. E.g.: docker exec -ti docker_start
sh
This is an alternative to using Docker Desktop to get to the
command-line interface.
Restarting an existing container. If you exit the
shell, reboot your computer, or destroy the terminal window, don't
worry. Everything is OK. Your container is still there. More
precisely, your processes (e.g., your command shell) are dead, but the
container's file tree is still there. You can get back into it by
saying:
docker start -i docker_start
That starts your existing named container
(docker_start
) again, using the same configuration and
command as specified in docker run that created the
container. Specifically, for this example, it launches a new sh shell
into the existing docker_start
container in interactive
mode (-i
). Again, you get a sh command prompt for a
shell running in the container, and everything should look just as it
did.
Remember if you use docker run again it creates a new container, or it gives you an error for running two containers with the same name. A new container might be OK if the only files you modified are in the bind mount directory, but it takes up more space, and any files you modified in the old container (e.g., to install missing packages with apt-get) do not appear in the new container.
Other Things You Can Do
- docker ps -a: List all of your current containers (not just the running ones).
-
docker rm NAME
: Destroy the container with NAME and its file system, killing any processes in the container. Any directories that were bind mounts in the container are preserved on your host. You might have to stop it first, e.g.: docker stop NAME -
docker image prune: Destroy any images that are not currently in use for any container.
For much more, see the Docker documentation or find your favorite tutorial site.
Another Example
Let's do another example of using an existing Docker image from Docker Hub. This one is a little simpler and describes what is happening within the terminal.
- docker run --name hello hello-world
Read what it says happened.
Note that the container doesn't keep running in this case:
docker psThe command that was run exited, and the container is done.
- Look at the images that you now have in Docker:
docker images - Look at
the Dockerfile
for this image
This image is built from the very basic image
scratch
. Thehello
script is copied to/
within the container. Then, thehello
executable is run. - Since the container stopped, let's try running a new container:
docker run --name hello hello-worldThat should give you an error because you already have a container named
hello
. - Run docker ps and docker ps -a to see the containers currently running and then all the containers. Even though the container exited, it still exists in the list of containers.
- Delete the container: docker rm hello
- Now, run the container again (as above).
What are options for the various docker commands?
Run docker run --help
- What does the
-d
option do? - What option automatically delete a container when it exits?
- Explain what this command is doing:
docker run --name docker_start -dp 80:80 docker/getting-started
Label the parts.
Sharing files with your container
Conveniently, a container's file tree may also include bind mounts, which attach folders/directories in the host file system so that processes in the container can operate on them. You select a directory that exists in your host file system and then command docker to bind mount it into a container's file tree, so that the directory is visible (usually at a different pathname) to processes running in the container. Then, if you destroy the container, then the mounted directory and its files survive in your host file tree. You can even share the directory among multiple containers over time or at the same time.
It is useful to bind mount your git local repository into your container, so that you can edit source files on the host using your favorite editor application, and then build/run them in the Linux container. You can run git commands on the host or in your container. But you have to do the git config and install your keys and so on in both places.
Using a Dockerfile
You have looked at a few Dockerfiles. Let's use one that we made: Dockerfile.
- Read the Dockerfile, so you have some idea of what it's doing.
- Build a container image on your host.
- Create an empty directory/folder and download/copy the Dockerfile into it with the name Dockerfile.
- Then in a terminal window on your host, enter the directory (with cd)
- Run docker build -t image397 .
Don't forget the . (current directory)! That build command creates an image based on the Dockerfile, and gives the image the name (tag)
image397
. If everything goes well you have a named image. You don't see it, but it is there.What are two ways to check if the image was created? Then, use those ways to check/confirm that the image was created.
- Run the image using:
docker run --name cs397 -it image397 bashThis created/ran the container
cs397
using the imageimage397
You're now "in" the container, using the bash shell.
Using bind mounts
- Create a directory/folder on your host to bind mount into your
container(s). Use the mkdir command or select new folder in the
menu of a window on the containing folder. Name the
directory
docker-share
. In a host terminal window, change into that directory (cd). - Create a container from the image and attach the bind
mount. Suppose the new directory/folder's path is
/Users/sprenkle/docker-share
. You want the bind mount to appear in the container file tree at/cs397
:
docker run --name cs397 --mount type=bind,source=/Users/sprenkle/docker-share,destination=/cs397 -it image397 bash
If you got an error about the name of the container being a duplicate, then remove the container using
docker rm cs397Make sure the path to the source has been updated appropriately.
This will create the container
cs397
using the imageimage397
WITH the directory/cs397
in the container mapped to yourdocker-share
directory.Again, you're now "in" the container, using the bash shell.
- cd to the
/cs397
directory. What is in that directory? - Run /usr/games/cowsay hi
What did that do?
- Run /usr/games/cowsay hi > hi.out
- View the contents of the directory.
- In another terminal (on the host machine, not in the container),
look at the contents of your
docker-share
directory. What do you see? - Create a new file in
docker-share
calledcool
. The contents of the file should becool
. - Back in the container, view the contents of the directory (should
still be in
/cs397
). How cool is that? - Run /usr/games/cowthink < cool
- Run /usr/games/cowthink < cool > cool.out
- View the contents of
cool.out
- In that other terminal on your host machine, view the contents of
docker-share
. Explain what is happening!
Analyze and Synthesize
Reflect on what you have done (and hopefully learned):
- What are the various components of Docker?
- What are the commands?
- When should you use the commands?
A More Real-World Use Case
Your boss assigned you to a project where you need to run a web application server (Apache's Tomcat). We'll see how this goes, with applying what you've learned!
- Find the appropriate image from Docker Hub
- Read the documentation to run the web application server. What is it telling you to do? Apply what you learned above.
- Put this war file in your
docker-share
directory. - Stop (and delete) the container. Run the container but give it a name, use port 8888, and mount your
docker-share
directory. - Go into the command-line interface and copy /cs397/sample.war into
/usr/local/tomcat/webapps
- Go to the Sample web app
Learning More
Return to the docker getting-started
image docker-start
container and continue through the
tutorial in the browser.