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. I provide specific instructions below.
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. But 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.
So: 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 an ordinary 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.
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.
You pulled the
image Getting Started and then ran a container based on
that image. Check out the image's web page. Then, look at
its Dockerfile
.
What is different?
Here is a Dockerfile.
These sample commands choose various names with 397 in them. You can use whatever names you want.
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 and type:
docker build -t dev397 .
Don't forget the . (you know -- the current directory)! That build
command creates an image based on the Dockerfile, and gives the image
the name (tag) dev397
. It downloads packages from the
network and generates a lot of output. xIf everything goes well you
have a named image. You don't see it, but it is there.
docker run --name cps510 --mount type=bind,source=/Users/chase/docker-share,destination=/cps310 -it dev310 bash
That creates a new container from the dev510 image, and names the container cps310. The new container is interactive (-i) and it has a terminal interface (-t), and it runs a bash shell process. The next command prompt you see should be the linux command shell (bash) in your new container. You can type Linux commands to it all day long. Change directory (cd) to your /cps310 bind mount, and go to town.
NOTE: newer versions of Docker Desktop for windows require explicit permission before mounting is possible. See this link for how to make your directory mountable.
Extra step for BuggyServer lab (p2). To be able to run an unsafe web server for this lab, you have to knock Docker Desktop over the head with another flag (--security-opt seccomp=unconfined) to get it to permit you to disable address space layout randomization (ASLR) protections. (Maybe don't use this variant unless you need to.)
docker run --name cps510-unsafe --security-opt seccomp=unconfined --mount type=bind,source=/Users/chase/docker-share,destination=/cps310 -it dev310 bash
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.
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 cps310 bash
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 cps510
That starts your existing named container again, using the same configuration and command as specified in docker run that created the container. Specifically, it launches a new bash shell into the existing cps510 container in interactive mode (-i). Again, you get a bash 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 (/cps510), 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: