--- title: "Docker and Bioconductor" author: "Dan Tenenbaum" date: "`r BiocStyle::doc_date()`" package: "`r BiocStyle::pkg_ver('docker.workshop')`" abstract: > Using Docker and Bioconductor. vignette: > %\VignetteIndexEntry{Docker and Bioconductor} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} output: BiocStyle::html_document: toc: true --- # What is Docker? I've found that it can be difficult to understand what Docker is just from a description. So let's dive right in and try to explain with a quick example. You'll need to bring up _shellinabox_ (see below) in order to try the example. ## Using _shellinabox_ You are welcome to [install Docker](https://docs.docker.com/installation/) on your own computer, but our Amazon Machine Images (AMIs) come with Docker already installed. We can access it using [_shellinabox_](https://code.google.com/p/shellinabox/) which provides most of the functionality of a terminal window in your web browser. To use it, open a new web browser window. Your _shellinabox_ URL is the same as your RStudio URL but with `:4200` added to the end. So if your RStudio URL is http://ec2-www-x-yyy-zz.compute-1.amazonaws.com Then your _shellinabox_ URL is: http://ec2-www-x-yyy-zz.compute-1.amazonaws.com:4200 Go to that URL and log in with username `ubuntu` and password `bioc`. ## A quick example before we do anything with docker, let's take a quick look at the machine we are on. what operating system is it running? to find out, enter the command cat /etc/*-release This tells us we are on Ubuntu Linux version 14.04. Let's take a look at what processes are running on the machine. Enter the command ps aux This will list a lot of processes, probably over 100, even though we are hardly doing anything on the machine. The typical machine runs a lot of processes just for its basic operation. Finally, let's see what files are in the current directory with ls Now let's try this with Docker. Start a docker container with the command: docker run -ti --rm debian:latest bash You'll see a prompt come up that looks something like this: root@bfd260cc3d02:/# The first thing to notice is that this prompt came up instantly. Let's try once again the command to see what operating system we are on: cat /etc/*-release This tells us we are on Debian version 8. Now let's look and see what processes are running, again with ps aux This time it only reports two processes, our `bash` shell and our `ps` command! Finally, type ls to see what files are present. They are totally different files than the ones we saw on the host machine. To clean up, type exit to leave the container. ## What just happened? We just demonstrated an essential aspect of Docker: the fact that processes in a Docker container are _isolated_ from those of the host machine. In fact, the Docker container we just started is running a totally different Linux distribution (Debian vs. Ubuntu on the host). And yet it's not a virtual machine; if it were, we'd expect to see a whole bunch of processes on it, just like on a normal machine. (Docker uses fairly recent advances in the Linux kernel called _cgroups_ and _namespaces_ to achieve this isolation.) So unlike a virtual machine, we can take advantage of all the speed and hardware of the host machine. So even though we were running a different Linux distribution in our container (Debian) than on our host (Ubuntu), the container was running in the same _kernel_ as the host. That explains why the container started up so quickly. Docker's filesystem is also isolated as we saw by running `ls`. So that's Docker in a nutshell. Isolated processes and filesystem (and network too!). When you see how Docker containers are created (in the next section) you'll understand all the shipping container metaphors that get tossed around with respect to Docker. ## Creating a container Let's say I have created a revolutionary new application that will change the world and bring me riches and fame. The application is a line of shell script that says: echo "Hello, world!" But we can pretend it is a very complex application that has a lot of operating system dependencies and is very difficult to build and install. So I'd like to ship my application as a Docker container, to save other users the pain of installing it. I'd create a file called `Dockerfile` and put the following in it: FROM debian:latest CMD echo "Hello, world!" Then I'd build the container with: docker build -t dtenenba/myapp . Now the container can be run as follows: docker run dtenenba/myapp Lo and behold, it spits out "Hello, world!". So now I can check my Dockerfile into GitHub and anyone can build and run it. I could also push it to [Docker Hub](https://hub.docker.com) (and I have) and then any user could get it (without needing to build it) by doing docker run dtenenba/myapp and the container will be pulled down from the server if it does not already exist on the host. Or you could explicitly pull it (but not run it) with docker pull dtenenba/myapp Dockerfiles contain all the instructions necessary to set up a container. Generally this consists of installing dependencies (usually with a package manager) and other prerequisites. See the full [Dockerfile reference](https://docs.docker.com/reference/builder/) for more infromation. (There's [another way](https://docs.docker.com/reference/commandline/commit/) to create a Docker container, but it's not recommended, so I won't discuss it. So there!) ## Dockerfiles and filesystem layers By the way, I highly recommend [The Docker Book](http://www.dockerbook.com/) by James Turnbull. It's available as a PDF and is updated frequently. In fact, I like the book so much that I am just going to quote from it fairly liberally about the Docker filesystem: > In a more traditional Linux boot, the root filesystem is mounted read- > only and then switched to read-write after boot and an integrity check > is conducted. In the Docker world, however, the root filesystem stays > in read-only mode, and Docker takes advantage of a > [union mount](https://en.wikipedia.org/wiki/Union_mount) to add > more read-only filesystems onto the root filesystem. A union mount is > a mount that allows several filesystems to be mounted at one time but > appear to be one filesystem. The union mount overlays the filesystems > on top of one another so that the resulting filesystem may contain > files and subdirectories from any or all of the underlying > filesystems. > Docker calls each of these filesystems images. Images can > be layered on top of one another. The image below is called the parent > image and you can traverse each layer until you reach the bottom of > the image stack where the final image is called the base image. > Finally, when a container is launched from an image, Docker mounts a > read-write filesystem on top of any layers below. This is where > whatever processes we want our Docker container to run will execute. > This sounds confusing, so perhaps it is best represented by a diagram. ![](filesystem.jpg "Docker filesystem") This diagram could be seen as another way to represent the following Dockerfile: FROM ubuntu RUN apt-get install -y emacs RUN apt-get install -y apache2 The purple boxes in the diagram (read from bottom to top) match the lines in the Dockerfile. When Docker builds the Dockerfile, it creates a new image (or filesystem layer) for each command in the Dockerfile. # Use Cases By now I hope you have a basic idea of what Docker is: an easy way to package up an environment to include some application and all of its dependencies. Now perhaps we have enough information to speculate about how Docker could help us: * **Reliability**: Now we have a way to distribute applications to other people, where the application should "just work." This should eliminate configuration and "works for me" problems. Workflows can be given to sysadmins/cluster admins as Docker containers and they don't need to know anything about what's in the containers, but can treat them as black boxes that take input and produce output. No more waiting for your sysadmin to install the latest version of R. [Galaxy](https://usegalaxy.org/) tools can also be defined as Docker containers. * **Reproducibility**: You can run a workflow today and run it again in a year using the _exact_ same environment. We'll discuss this more later in the context of the Bioconductor containers. * **Testing**: My workspace on my local computer changes every day as I add and remove software, but for testing I need a consistent environment. * **Separation of concerns**: Some applications consist of multiple processes; thse can each run in their own Docker container. We'll discuss how to do this a little later. * **Cloud deployment**: Both [Amazon](https://aws.amazon.com/ecs/) and [Google](https://cloud.google.com/container-engine/) are supporting Docker as a first-class citizen on their cloud platforms. # Docker and Root Privileges One big issue with Docker is that you need to be _root_ to run it, because the kernel features that make Docker work require root privileges. We haven't seen it in our examples so far because the _ubuntu_ user on the AMI is a member of a group that has root privileges for certain operations. Normally, however, all _docker_ commands must be run as root or prepended by _sudo_. (This is not necessary on Mac or Windows.) This does present some problems. Not everyone (especially on shared systems) has root privileges. Sysadmins may be reluctant to run a container with unknown contents as root. # What if I'm not on Linux? You can still run Docker on Windows and Mac, via a lightweight virtual machine called [Boot2docker](http://boot2docker.io/). Just follow the instructions on the [Docker installation](https://docs.docker.com/installation/) page. Windows 10 will include a form of Docker allowing you to run Windows containers. # Breaking into the black box You might be thinking, "well, it's nice that I can have an application that says 'Hello world', but how do I get Docker to compute on my data? How do I run a web application on docker?" ## Working with local files Here's a quick Dockerfile that performs some operation on data on my local computer. In this case, I want to make value judgements about the files in a directory. The following Dockerfile will do it: FROM debian:latest VOLUME /data WORKDIR /data CMD for i in `ls`; do echo $i is awesome; done I can build it with docker build -t dtenenba/voldemo . And run it like this: docker run -v /etc/:/data dtenenba/voldemo The `-v` switch takes two directories (separated by a colon); the first one is a directory on my local filesystem, the second can be thought of as a 'mount point' for that directory inside the container. Affirmingly, the above command tells me that every file in my `/etc` directory is awesome. In this case, Docker is just listing the files, but it can perform write operations as well. (Since Docker runs as root by default, its output files may not have the permissions you want; read [Rocker's page](https://github.com/rocker-org/rocker/wiki/Sharing-files-with-host-machine) on the subject for tips on getting around this). ## Exposing the network This Dockerfile is a very simple web server, but it could be a complex web application: FROM debian:latest RUN echo "It works!" > index.html RUN apt-get update RUN apt-get install -y python EXPOSE 8000 CMD python -m SimpleHTTPServer 8080 If you build it: docker build -t dtenenba/webdemo . ... and run it: docker run --name webserverdemo -p 8080:8080 dtenenba/webdemo . Similarly to the `-v` switch, the `-p` switch maps a port on the host to one on the container. You can then open it in a web browser and see the riveting message. ### Knowing which URL to open If you created this Dockerfile on your own Linux machine, you can just go to [http://localhost:8080](http://localhost:8008). If you are using boot2docker you can do something like this to get the URL: echo http://$(boot2docker ip):8000 If you are on this AMI, you can just append `:8080` to your RStudio URL, so something like: http://ec2-www-x-yyy-zz.compute-1.amazonaws.com:8080 ### Cleaning up To stop the web server, you can press control-C, or open another window and issue the command: docker rm -f webserverdemo # Composing complex applications from multiple containers A typical web application might involve several processes; for example, the Bioconductor new package tracker consists of a web application written in Python, a MySQL database, and an SMTP mail server. Containers can be [linked together](https://docs.docker.com/userguide/dockerlinks/) to facilitate scenarios like this. Two containers can communicate with each other over network ports (without exposing their networks to the host!) You can make an application like this out of multiple containers, one for each process. (A docker application _can_ consist of multiple processes inside a single container, but it's considered a best practice to just run one process per container.) Even better, for common applications like MySQL server, you can just pull down a pre-existing container that's ready to go. You don't need to create and build the MySQL server yourself. Similarly, there's an off the shelf web-app container that just receives email sent to it and displays it in a browser, ideal for testing the email functionality of a web application. Tying it all together is a tool called [Docker Compose](https://docs.docker.com/compose/) (formerly known as Fig). This tool is not part of Docker and needs to be installed separately. With a [YAML file](https://github.com/dtenenba/bioc_docker/blob/master/tracker.bioconductor.org/fig.yml), we declare the dependencies between containers in this application. Here's the real-life YAML file for the multi-container Docker app we use for testing the new package tracker: ```{r, echo=FALSE, comment=NA} library(httr) cat(x <- content(GET("https://raw.githubusercontent.com/dtenenba/bioc_docker/master/tracker.bioconductor.org/fig.yml"))) ``` I won't dwell too much on details here (the complete app is [on GitHub](https://github.com/dtenenba/bioc_docker/tree/master/tracker.bioconductor.org)), but this sets up the three containers and all the information they need to communicate with each other. Each container can access the relevant information by means of environment variables. A single command (`docker-compose up`) turns on all three containers. See the [docker-compose.yml reference](https://docs.docker.com/compose/yml/) for more on this declarative syntax. # Docker and R/Bioconductor We've seen how Docker images can be built on top of one another: our "Hello, World" application was built on top of _debian:latest_. By the same token, the Bioconductor images are built on top of R and RStudio images provided by the [Rocker](http://dirk.eddelbuettel.com/blog/2014/10/23/) organization. The [full documentation](http://bioconductor.org/help/docker/) for these images is at the Bioconductor website. For devel and release (and going forward, older releases), the following containers are available (the actual container names start with `release_` or `devel_` and end with one of the following): * `base`: R and the `BiocInstaller` package (providing `biocLite()`) * `core`: `base` plus core infrastructure packages * `flow`: `core` plus all packages with the FlowCytometry biocView * `microarray`: `core` plus all packages with the Microarray biocView * `proteomics`: `core` plus all packages with the Proteomics biocView * `sequencing`: `core` plus all packages with the Sequencing biocView ## Starting the containers There are several ways to interact with the containers. The default way is to use RStudio Server. Entering the following command: docker run -p 8787:8787 --rm bioconductor/devel_base Will make RStudio Server available on port 8787 of the host. So if your AMI URL is http://ec2-www-x-yyy-zz.compute-1.amazonaws.com Add `:8787` to the end: http://ec2-www-x-yyy-zz.compute-1.amazonaws.com:8787 The RStudio login is `rstudio` and the password is `rstudio`. You can also start command-line R with this command: docker run -ti --rm bioconductor/devel_base R Or if you plan to run other commands besides R inside the container, start a bash shell: docker run -ti --rm bioconductor/devel_base bash ## Reproducibility The key to reproducibility is to know exactly which Docker image you are using so you (or others) can reuse it later. Bioconductor's Docker images are tagged. For example, [here](https://registry.hub.docker.com/u/bioconductor/devel_base/tags/manage/) are the available tags for the `devel_base` image. There are three kinds of tags: * Date tags (example: 20150713). The images are rebuilt every day. If the build is successful, the image is tagged with a string representing the current date. _This is the only kind of tag you should rely on for reproducibility._ * Version tags (example: 3.2). With each daily build, the version tag is applied. This number refers to the Bioconductor version. * The string `latest`. This tag is applied to each daily build of an image. Note that when running or pulling an image, by default you are pulling the `latest` tag of that image, unless you specify another tag, with (for example): docker pull bioconductor/devel_base:20150713 So an image can have multiple tags. If today is July 13, 2015, then the most recent `devel_base` image will be tagged `20150713`, `3.2`, and `latest`. The tag `20150713` will **always** apply to the same image, but the other two tags do not. So for purposes of reproducibility, you should always pay attention to the date tag of the image you're using. If you didn't start the image with a specific tag, you can determine the tag of a running container with the `docker ps` command, for example: docker run -ti --rm bioconductor/devel_base And then in another window: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0af597a23632 bioconductor/devel_base:20150713 "bash" 6 seconds ago Up 6 seconds 8787/tcp happy_yalow This tells me that I am using the image tagged 20150713. # References * [Dockerfile Reference](https://docs.docker.com/reference/builder/) * [`docker run` reference](https://docs.docker.com/reference/run/) * [Docker command line reference](https://docs.docker.com/reference/commandline/cli/) * [Docker Compose](https://docs.docker.com/compose/) * [Bioconductor Docker Page](http://bioconductor.org/help/docker/) * [Reproducible Research using Docker and R](https://benmarwick.github.io/UW-eScience-docker-for-reproducible-research/#1)