What is Docker?
Docker takes advantage of Linux kernels' ability to run applications in containers, which are sometimes described as "chroot on steroids." Containers provide each application an independent runtime environment, while avoiding the overhead of a full-fledged virtual machine.
Each container gets its own virtual file system, process listing and network stack; however, containers share the OS kernel with each other and the underlying host. In this respect, the isolation provided by containers is less robust than that of real virtual machines, which have independent kernels and run on top of a hypervisor. Yet, sharing the kernel allows containers to run faster and offers management features that are difficult to accomplish with traditional virtualization.
An application distributed as a Docker image incorporates all the dependencies and configuration necessary for it to run, eliminating the need for end-users to install packages and troubleshoot dependencies. This approach allows developers to be certain that if the application worked in dev, it will work in production. Docker provides the tools necessary to build, run and manage applications packaged as Docker images.
Sounds a bit too abstract? The best way to understand Docker is to experiment with it.
Running a Dockerized Application
If you're running Linux as your base operating system, installing Docker is very easy (e.g., "sudo apt-get install docker.io"). If you're running OS X or Windows, you can install Docker as well, though with a few additional steps. Just follow instructions appropriate for your OS.
Setting up JSDetox the traditional way isn't very difficult, but it requires installing a bunch of packages, which might conflict with other software you've already installed. Not a big deal, but Docker provides an easier way to distribute the application, so that you can run it without worrying about having the right dependencies.
Once Docker is installed on your Internet-connected system, you can run my JSDetox application image by typing:
sudo docker run --rm -p 3000:3000 remnux/jsdetox
Then, launch your web browser and point it to http://localhost:3000. If using Windows or OS X, the steps are a bit more awkward, because you'll need to connect to the IP address of the virtual machine that Docker will have set up on your system for hosting its containers; this is done with the help of a tool called boot2docker.
The "docker run" command above will run the image called "remnux/jsdetox". If the image is not already present on your system, the tool it will look for it in Docker's image registry and automatically download it. If your system already has some of file system layers upon which "remnux/jsdetox" depends, Docker will only download the missing layers. If I update the image, you can update your local cache of the image by running the command "sudo docker pull remnux/jsdetox".
A Docker container is an instance of a Docker image. The distinction between an image and a container allows you to have a single image that represents the application, while being able to launch multiple isolated instances of the app as containers on a single host. The "--rm" parameter in the command above directs Docker to automatically remove the container once it finishes running. This gets rid of any changes the application may have made to the local environment when it ran, but does not remove the cached image file that represents the app on your system.
The "-p 3000:3000" command directs Docker to map TCP port 3000 from the container to TCP port 3000 on localhost. When JSDetox runs, it listens locally on TCP port 3000, so you can connect to it as http://localhost:3000. Since the app will be running in a container, that port will be inaccessible from your underlying system unless you direct Docker to expose it.
To stop a Docker container, such as the one that we just launched, first determine its container ID by running the "sudo docker ps -l" command. Then run the command "sudo docker stop container-id" by specifying the appropriate container ID.
Building a Docker Image of an Application
One way to build a Docker image for the desired application is to start with a base image for the core OS, run it in Docker, install the necessary tools and make the configuration tweaks. You can then use the Docker "commit" command to save the container to an image and, if you wish, publish it in Docker's public registry or your own private registry.
Another approach is to document the steps necessary to build your image in a properly-formatted Dockerfile file. This approach makes sure that all aspects of installing and configuring the application are properly documented and can be repeated. For example, take a look at the Dockerfile I created for the JSDetox image. It's available on the REMnux Github page in its full glory, but here key excerpts:
The instruction above directs Docker to use a public instance of the Ubuntu 14.04 image as the baseline for the new image, so I don't have to build my own OS image.
USER root RUN apt-get update && apt-get install -y git ruby ruby-dev ...
The instructions above direct Docker to update core OS packages and install additional packages that JSDetox requires to operate properly, running the "apt-get" command within the container with administrative privileges.
RUN groupadd -r nonroot -g 433 && useradd -u 431 -r -g nonroot ...
The instructions above add a group and user named "nonroot" to the image, so that JSDetox can be launched without administrative privileges.
USER nonroot WORKDIR /home/nonroot RUN git clone https://github.com/svent/jsdetox.git
The instructions above download JSDetox from its Github repository as the user "nonroot".
WORKDIR /home/nonroot/jsdetox RUN bundle install
The instructions above install the JSDetox application.
USER nonroot WORKDIR /home/nonroot/jsdetox CMD ./jsdetox -l $HOSTNAME 2>/dev/null
The instructions above specify how Docker should launch the JSDetox application when the user runs its container.
Once you're ready to build the image, run the command "sudo docker build -t=image-name ." from the directory where the Dockerfile is located. You can can also publish the Dockerfile on Github or BitBucket and link it to your account on Docker's public registry. You can even direct Docker to automatically update your application's image when you change the Dockerfile.
The Benefits of Dockerizing Applications
In the example of the JSDetox image above, you can see that the application maintainer needs to carefully think through the steps needed to install and configure the app. This is easier to do with Docker than in the traditional scenario, because Docker allows the developer to specify all the necessary dependencies in the Dockerfile and to test the image before distributing it to others.
If the developer changes the Dockerfile, Docker can automatically update the corresponding application image in its public repository, making sure that users will get the latest update the next time they run the image. They simply need to run the application by specifying the appropriate Docker image without worrying about OS installation, dependencies, configuration and updates.
A Redhat article outlines additional benefits that Docker offers, which include:
- Rapid application deployment
- Portability across machines
- Version control and component reuse
- Simplified maintenance
This introductory posting uses a relatively simple scenario to demonstrate how one might get started with Docker, but there are many more use-cases for running complex applications in dev environments and, potentially, for deploying sophisticated software into production. Docker's initial simplicity is misleading. The more I experiment with Docker, the more I recognize that it offers more than the sum of its parts.
To learn more about this topic, tune into the webcast I recorded to explain How to Run Linux Malware Applications as Docker Containers. If you'd like to understand the security pros and cons of these technologies, read my article Security Risks and Benefits of Docker Containers.