Lately I’ve been reading a bit about how people are using Docker to run apps in high isolation and portability with low overhead, Vagrant for easy to replicate dev environments and speedy setup (read: no more nasty large images to download and update when all your co-workers want the same VM). And since we have a fun little project requiring a special service in the works that also requires some portability (for dev and production), I took it upon myself to try my hand at running a simple (seriously, I copied it off their front page) Sinatra demo, inside of a Docker container, inside of Vagrant, on OSX.
Now, for the sake of posterity and perhaps helping the random person out there with a similar mentality and technical environment as I… Let’s see how I did!
Docker Initial Considerations
- There is currently no Mac Binary, you need to run Docker on Linux, which in turn means we need to use a VM. The VM we’ll use here is VirtualBox.
- Docker needs Linux kernel 3.8 and instead of picking a distro that doesn’t have it and then compiling it, we’ll use Ubuntu Raring.
- Docker also needs AUFS (read more here for the geeky parts). Ubuntu Raring however does not have this out of the box, so we’ll need to install it.
Setting Up Vagrant and Docker with a Vagrantfile
Shimmy on over to http://downloads.vagrantup.com/, grab the relevant tag/package for your system and install it. Once installed, create a directory you can use for your Vagranty goodness (for the sake of this guide we’ll be using
~/vagrant/.) This will be a shared folder that we can access from inside the VM, the idea being that you’ll be creating some config files in here and accessing them once the VM is up.
Inside this new folder go ahead and drop this Vagrantfile. Vagrantfiles are how Vagrant knows what sort of VM to spin up. If you read this short ruby script (all Vagrantfiles are Ruby, which is nice) you’ll see a few things happening.
config.vm.box = "raring" - is the name of our box
config.vm.box_url - sets where Vagrant will fetch the box for Ubuntu Raring
config.vm.forward_port - is where we tell vagrant that we want to take the VM’s local port 5000 and forward to the host’s port 5000
config.vm.customize - is where we’d define any special attributes for the VM we’re starting. In this case I wanted to make sure we had 512mb of ram (mainly so it’s procured a bit quicker, this could be tinier in all honesty)
* There are a few lines checking if a
.vagrant file exists, so we don’t run some installation instructions every time we start up the vm.
* I’ve broken each command we want to run out into a string which we’ll concatinate and then pass to…
config.vm.provision - This takes the string we pass it and runs it in the shell as soon as the VM is booted.
Now that we understand it let’s fire this puppy up. Inside our
~/vagrant/ folder run:
$ vagrant up
As the fireworks happen you will notice a couple things. Vagrant will download the image we specified in our Vagrantfile, run the scripts we passed to
config.vm.provision. Eventually you’ll be left at your prompt where you started. So let’s see if this thing works. In your terminal run:
$ vagrant ssh
If things are up and running it’ll open an SSH session into our fresh VM and we’ll be able to test things out.
Our first check will be to see that Docker was installed correctly. Run the following:
$ which docker /usr/bin/docker
It should return the path to the Docker binary. Which leads us to our next step.
Building Our Sinatra Image With A Dockerfile
Go ahead and grab the Dockerfile we’re going to use and put it in
~/vagrant/ so that it’s shared with our VM. Take the time to open and read the Dockerfile while you’re at it so you both understand what we’re building and also so that you can see how simple these are to create.
This is what it will do:
FROM ubuntu - Tells docker what image to use as our starting point.
RUN apt-get install -y ruby rubygems git - RUNs this command to install ruby, rubygems, and git.
RUN git clone https://gist.github.com/11d45cb1767629962c5f.git /opt/*
sinatra/ - Clones the gist to our sinatra folder where we’ll run our server script.
RUN gem install bundler - Kicks off the bundler install so we can grab any other gems we need (namely sinatra).
EXPOSE :5000 - Exposes port 5000 to the host OS
RUN cd /opt/sinatra && bundle install - Changes directory into our Sinatra folder and on completion installs all the gems we need.
CMD foreman start -d /opt/sinatra - This is a critical piece of the puzzle. This is what Docker runs at the completion of the build. The last CMD in the Dockerfile is the only CMD Docker cares about, and when we provision an image later it will cache every step it’s completed before (which makes it real fast), and finishes by running that final CMD.
Let’s try and build an image. Run this on your VM:
$ sudo docker build -t sinatra /vagrant/.
Docker will procede to download the base image, and run the instructions in our Dockerfile on said image, and store snapshots of each step in the process. Provided everything then went well you’ll see something like this at the bottom:
---> Running in <some other hash> ---> <some hash> Successfully built <some hash>
We can now fire up our Sinatra container by entering the following:
$ sudo docker run -d sinatra <container id>
When the container is provisioned successfully it will return a container id. Now that it’s up and running let’s try it out. Browse to
localhost:5000 and you should see
hello world. If so then you have met with great success! Before we leave here are a few usefull commands to make use of:
sudo docker ps -l - Lists all running, or the most recent containers
sudo docker kill <id hash> - Take one of the ids you’ve found from
ps -l and shut it down
sudo docker restart <id hash> - Same as the prior command except restart the container
sudo docker images - Lists all the local images you have at your disposal