↳ Click the following to start the exercise.
If we worked on our script for several more years and took advantage of other capabilities of the Linux kernel, such as cgroups and namespaces, we could overcome many of the limitations we saw with our homegrown containers. Thankfully, someone has done this work for us.
Pull an ubuntu focal image and create a VM called, docker-vm
.
bakerx pull focal cloud-images.ubuntu.com
bakerx run docker-vm focal
vm pull ubuntu:focal
vm run docker-vm ubuntu:focal
📹 Watch: Video demo of these steps, then try to complete these steps on your own:
The recommended method for installing docker on ubuntu can be found here. But in the interest of time: 😬
curl -sSL https://get.docker.com/ | sh
After installation, it is recommended you allow docker to be run without needing sudo. IMPORTANT You'd need to restart your shell (exit and log back in) to see the changes reflected.
sudo usermod -aG docker $(whoami)
Verify you can run docker:
docker run hello-world
Pull an ubuntu image.
$ docker pull ubuntu:18.04
Check the size of the images.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 ccc6e87d482b 6 days ago 64.2MB
hello-world latest fce289e99eb9 12 months ago 1.84kB
Let's create a simple container.
docker run ubuntu:18.04 sh
Unfortunately, that doesn't quite do what we want, because a pseudo-tty allocated to the container, so we can use the terminal.
$ docker run -t ubuntu:18.04 sh
# ls
exit
.exit
^C
Unfortunately, that's still not enough. Docker isn't necessarily the most intuitive system, you see. You also need to allow input (-i
Keep STDIN open even if not attached).
$ docker run -it ubuntu:18.04 sh
Finally, we can get to a terminal, and see we can run shell commands in our own private snapshot of the ubuntu image.
$ docker run -it ubuntu:18.04 sh
# ls
# rm -rf --no-preserve-root /
# exit
Oh no, what have we done? Is everything okay?
$ docker run -it ubuntu:18.04 sh
We can create our own images. Create a "Dockerfile" and place this content inside:
FROM ubuntu:18.04
RUN apt-get -y update
# update packages and install
RUN apt-get install -y openjdk-11-jre-headless wget curl unzip
RUN apt-get -y install git
RUN apt-get -y install maven
ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64
Build the docker image, and name it "java11".
docker build -t java11 .
We can see our image listed, now:
docker images
Now, let's use it to run mvn
to check our environment setup.
docker run java11 mvn --version
Look at all the containers you've created by running commands above.
docker ps -a
The container "graveyard" contains all the dead containers that ran a process, then exited.
If we want a container to stick around, we need it to run in daemon mode, but adding the -d
arg. We also provide a name for easy reference.
docker run --name test-d -it -d ubuntu:18.04
Let's use an one-liner to write a file to our running container.
docker exec -it test-d script /dev/null -c "echo 'Hello' > foo.txt"
Make sure we can see change. Note we add -t
to improve the output of ls
:
docker exec -t test-d ls
Now, let's commit this to our image. Any new container will now have 'foo.txt' inside it.
docker commit test-d ubuntu:18.04
We can confirm that new containers do indeed have 'foo.txt' inside:
$ docker run -t ubuntu:18.04 ls
bin dev foo.txt lib media opt root sbin sys usr
boot etc home lib64 mnt proc run srv tmp var
Imagine you wanted to create a simple build script and run against an image. It could be incredible tedious to create a new image per project, just to add the build script. Furthermore, getting data out of a container could be unwieldy. Alternatively, we can use volumes, to share our filesystem of our host with a container.
docker run -v /home/vagrant/:/vol java11 ls -a /vol/
We should be able to see our host's directory home directory. Now, we can have a simple "escape hatch" to get data in and out of the container at the cost of losing true immutability.
In your host VM, create 'build.sh' and place the following inside:
git clone https://github.com/CSC-326/JSPDemo
cd JSPDemo
mvn compile -DskipTests -Dmaven.javadoc.skip=true
chmod +x build.sh
docker run -v /home/vagrant/:/vol java11 sh -c /vol/build.sh
We can confirm the code is greatly out of date and does not work with Java 11!
[INFO] 31 errors
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 29.052 s
[INFO] Finished at: 2020-01-22T22:06:57Z
[INFO] ------------------------------------------------------------------------
Recall we want to maintain efficiency and isolation. Here is how Docker enables this.
- A docker image contains a set of layers, which can be composed for efficient storage on the system.
- An image can be based on other images (
FROM ubuntu:18.04
). - You can build your own images by running commands inside a Dockerfile.
- A base image is usually created with hand-crafted rootfs (
FROM scratch
...COPY /rootfs /
). - More advanced ways to build images, include using the Builder pattern, for multi-staged builds.
- A container contains an overlay of the image's rootfs.
- Containers are generally stateless, that is any change to a container has no effect on its image; however, you can commit a change to a new image.
- A docker container only stays alive as long as there is an active process being run in it. You can keep a long running container by running a process that does not exist (a server), or running in daemon mode (
-d
). - A container cannot generally access other processes, the host filesystem, or other resources. The entry point is PID 1.
- While greatly useful, running programs inside containers can result in many different quirky behavior.
If you'd like to have docker on your system, see options for docker on your system for different choices you have available for your platform.