In this post, we will take a look at the process of converting a slightly more complicated use of Vagrant and Ansible over to Docker. In a previous post we covered how to adapt a simple, standalone Grails application. This time, we’ve updated the petclinic sample to use a standalone postgres installation as its data source.
Updates to Petclinic
In order to make this example more complex, I’ve updated the petclinic sample app to use postgres rather than an in-memory database for persistence. These changes can be seen here.
The Vagrant Version
First, let’s kick off the Vagrant version and see what’s changed.
$ git clone https://github.com/influenza/blog-snippets.git $ cd blog-snippets/VagrantToDockerPartTwo/Vagrant $ vagrant up $ vagrant ssh vagrant@precise64:~$ cd /opt/petclinic/app vagrant@precise64:/opt/petclinic/app$ ./grailsw run-app
Again, this will take a long time - especially for the first run. While the dependencies download, go ahead and open another terminal up and log in to postgres:
$ cd blog-snippets/VagrantToDockerPartTwo/Vagrant $ vagrant ssh vagrant@precise64:~$ psql -U grails petclinic Password for user grails: <password is 'super secure'> psql (9.3.4) Type "help" for help. petclinic=#
Once the grails app finishes loading and you see ‘Server running.’, go ahead and browse to http://localhost:8080/petclinic to prove to yourself that it’s working. Nothing new here from last time. Now, back in the postgres session, let’s take a look at the data that has been generated by the petclinic boot strap files:
As you can see, we now have several tables in the default schema related to the petclinic app, some with data already present!
Now, let’s look at the Vagrant file used for this project. It’s identical to our
last version. The meaningful
changes here can be seen in the
This playbook is substantially larger than the previous version. In order to install and setup the postgres package, I wanted to use the ‘apt_repository’ directive in Ansible, which required some additional python dependencies. Additionally, the database creation and setup (add a user, allow local access, add the DB) requires a number of additional steps. Overall, the playbook is still straight forward.
Before moving on to the Docker version, make sure that you stop the vagrant VM. It binds to the same port we will be using for our Docker version, so we need to free up that port.
$ vagrant halt
This will save the VM’s state and halt execution.
The Docker Version
First, make sure that the docker daemon is running. To do this on Arch:
sudo systemctl start docker
In the spirit of Docker’s design principles, we will be using a process per container. We could stack our database instance inside the same container as the grails application, but that would be breaking the model!
Let’s start the postgres container:
$ cd blog-snippets/VagrantToDockerPartTwo/Docker/Postgres $ sudo docker build -t postgres . $ sudo docker run -d --name petclinicData postgres
Next, we start up the petclinic application container and link the two together
$ cd blog-snippets/VagrantToDockerPartTwo/Docker/Petclinic $ sudo docker build -t petclinic2 . # <-- '2' as this is the fork $ sudo docker run -t -i -p 8080:8080 \ --name petclinicApp \ --link petclinicData:database \ petclinic2 /opt/petclinic/app/grailsw # <-- remember the suffix '2' grails> run-app
At this point, once the application finishes loading, you can again visit http://localhost:8080/petclinic to see that the application is running as expected.
The commands that we use are a bit different from the ones used last time. We use the new support for container names and linking so that the petclinic application container can use inter-container communication to access the database, rather than needed to hit the host network. Additionally, using names means that we don’t need to keep track of the container UUIDs as they are emitted! I love UUIDs just as much as the next robot, but they can be a bit tricky to remember. For full details on names and linking, check out the docs.
Here’s the Postgres Dockerfile in full:
This is a pretty simple Dockerfile, mainly owing to the base image we used. The only changes necessary from that base image are the addition of our grails user and the creation of the petclinic database. Note that this image is not suitable for use in production. It is open to the world with weak passwords.
Next, let’s take a look at the petclinic application’s Dockerfile:
The only notable change here is line 13, where we use a bit of a hack to point the petclinic DataSource file to a linked container rather than localhost.
The reason this works, is that since Docker version v0.11, linked containers are mapped to
entries in the container’s
/etc/hosts file. When we run the petclinic application specifying
that the petclinicData container be linked as
database, an entry will be added that aliases
database to the appopriate ip address. See this
This is, unfortunately, a hack. Doing a search and replace on a file that is under source control
can lead to a number of issues. A better way to handle this situation would be to change the
petclinic application to allow for an environment variable override of the database host, and
ENV directive in our petclinic Dockerfile.
Luckily, I’ve done just that!
Now that we can update the database host without editing source code, we can safely link a
volume containing the petclinic application on our local machine. In preparation for that, let’s
override-environment branch from the petclinic fork.
$ cd ~/tmp $ git clone https://github.com/influenza/grails-petclinic.git petclinic-fork $ cd petclinic-fork $ git checkout override-environment
Now, we’ll update the Dockerfile to use an environment variable rather than running a search
and replace across
Here’s the new version:
Note that the only change is line 13 - we now use
ENV rather than
RUN-ing sed. If you’re
following along from scratch (rather than from the
blog-snippets repo), be sure to rebuild the
petclinic2 image with
sudo docker build -t petclinic2 .!.
Let’s remove the previous petclinicApp container:
$ sudo docker rm petclinicApp # <-- container name, not the image name
Ensure the postgres container is still running:
$ sudo docker ps -a | grep petclinicData # Check for up status - if not... $ sudo docker start petclinicData # <-- names! So awesome :-)
Now let’s run the petclinic application container again, this time linking to our local copy of the application source.
$ cd blog-snippets/VagrantToDockerPartTwo/Docker/Petclinic $ sudo docker build -t petclinic2 . $ sudo docker run -t -i -p 8080:8080 \ --name petclinicApp \ --link petclinicData:database \ --volume ~/tmp/petclinic-fork:/opt/petclinic/app \ petclinic /opt/petclinic/app/grailsw grails> run-app
Note - Since we are using our local grails application directory, the dependencies will be downloaded again the first time you run this command
Once the application finishes loading, you can again verify it is working. Additionally, now that we have used a volume to link our local source to the Docker container, we can make updates on the fly locally and see them reflected in the application.
To give this a go, try editing
~/tmp/petclinic-fork/grails-app/views/clinic/index.gsp line 13 to
say ‘Display most vegetarians’ instead of ‘Display all veterinarians’. Once the file is saved, reload
your browser behold your handiwork.
An Aside: Docker’s Containers and Images
In the section above, we iterated on the initial Dockerfile to create something less hacky and
easier to work with. This led to building a new image based on the changes. After the build, we
docker run with a bunch of parameters to wire it up just the way we like it. Since we
named the newly running container, we can easily use
docker stop, and
docker attach to interact with this same container, meaning we don’t need to type out this
giant command line invocation each time we want to interact with it, nor do we need to remember
or store container UUIDs.
For instance, let’s say we decide to take a break from our serious petclinic development and exit out of the grails shell:
grails> exit $ <dropped back to the local host's shell>
After our break, we can easily resume where we left off with:
$ sudo docker start petclinicApp $ sudo docker attach petclinicApp grails>
Awesome! No need to specify the volume bindings or container links! If the postgres container isn’t running and we try to start the petclinic2 container (which is linked to it), docker will give us a helpful reminder:
$ sudo docker stop petclinicApp petclinic2 $ sudo docker stop petclinicData petclinicData $ sudo docker start petclinicApp Error: Cannot start container petclinicApp: Cannot link to a non running container: /petclinicData AS /petclinicApp/database 2014/05/18 16:19:37 Error: failed to start one or more containers
The error tells us in plain english that we can’t start the container we requested, as we cannot link to the non-running container, petclinicData.
This workflow is SO MUCH FASTER than the Vagrant+Ansible+Virtual box based approach.
With container linking support and the vast catalog of images available in the Docker index, we can easily adapt most Vagrant+Ansible configurations to use Docker instead. The performance benefits of the migration will pay for itself after just a single day of use. Try it out for yourself for a few days. You’ll find that the speed and simplicity of working with docker containers will make working with VMs extremely painful.
During the course of the Docker experimentation I’ve been doing, I’ve ended up with a lot of containers that I’m not using. If you want to remove all but the running docker containers, here’s a command to do that:
$ sudo docker ps -a | tail -n +2 | cut -d ' ' -f 1 | xargs -L 1 sudo docker rm
After my initial post on this topic, I received a lot of feedback about new Vagrant support for using Docker as a vm provider.
For a look at deploying a Grails application into Tomcat running in a Docker container, see this post.