12 min read

In this article by Chad Thompson, author of Vagrant Virtual Development Environment Cookbook, we will learn that many software developers are familiar with using Vagrant (http://vagrantup.com) to distribute and maintain development environments. In most cases, Vagrant is used to manage virtual machines running in desktop hypervisor software such as VirtualBox or the VMware Desktop product suites. (VMware Fusion for OS X and VMware Desktop for Linux and Windows environments.)

More recently, Docker (http://docker.io) has become increasingly popular for deploying containers—Linux processes that can run in a single operating system environment yet be isolated from one another. In practice, this means that a container includes the runtime environment for an application, down to the operating system level. While containers have been popular for deploying applications, we can also use them for desktop development.

Vagrant can use Docker in a couple of ways:

  • As a target for running a process defined by Vagrant with the Vagrant provider.
  • As a complete development environment for building and testing containers within the context of a virtual machine. This allows you to build a complete production-like container deployment environment with the Vagrant provisioner.

In this example, we’ll take a look at how we can use the Vagrant provider to build and run a web server. Running our web server with Docker will allow us to build and test our web application without the added overhead of booting and provisioning a virtual machine.

(For more resources related to this topic, see here.)

Introducing the Vagrant Provider

The Vagrant Docker provider will build and deploy containers to a Docker runtime. There are a couple of cases to consider when using Vagrant with Docker:

  • On a Linux host machine, Vagrant will use a native (locally installed) Docker environment to deploy containers. Make sure that Docker is installed before using Vagrant. Docker itself is a technology built on top of Linux Containers (LXC) technology—so Docker itself requires an operating system with a recent version (newer than Linux 3.8 which was released in February, 2013) of the Linux kernel. Most recent Linux distributions should support the ability to run Docker.
  • On nonLinux environments (namely OS X and Windows), the provider will require a local Linux runtime to be present for deploying containers. When running the Docker provisioner in these environments, Vagrant will download and boot a version of the boot2docker (http://boot2docker.io) environment—in this case, a repackaging of boot2docker in Vagrant box format.

Let’s take a look at two scenarios for using the Docker provider. In each of these examples, we’ll start these environments from an OS X environment so we will see some tasks that are required for using the boot2docker environment.

Installing a Docker image from a repository

We’ll start with a simple case: installing a Docker container from a repository (a MySQL container) and connecting it to an external tool for development (the MySQL Workbench or a client tool of your choice). We’ll need to initialize the boot2docker environment and use some Vagrant tools to interact with the environment and the deployed containers.

Before we can start, we’ll need to find a suitable Docker image to launch. One of the unique advantages to use Docker as a development environment is its ability to select a base Docker image, then add successive build steps on top of the base image. In this simple example, we can find a base MySQL image on the Docker Hub registry. (https://registry.hub.docker.com).The MySQL project provides an official Docker image that we can build from.

We’ll note from the repository the command for using the image: docker pull mysql and note that the image name is mysql.

  1. Start with a Vagrantfile that defines the docker:
    # -*- mode: ruby -*-
    # vi: set ft=ruby :
     
    VAGRANTFILE_API_VERSION = "2"
    ENV['VAGRANT_DEFAULT_PROVIDER'] = 'vmware_fusion'
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
    config.vm.define"database" do |db|
       db.vm.provider"docker"do |d|
         d.image="mysql"
       end
    end
    end

    An important thing to note immediately is that when we define the database machine and the provider with the Docker provider, we do not specify a box file. The Docker provider will start and launch containers into a boot2docker environment, negating the need for a Vagrant box or virtual machine definition. This will introduce a bit of a complication in interacting with the Vagrant environment in later steps.

    Also note the mysql image taken from the Docker Hub Registry.

  2. We’ll need to launch the image with a few basic parameters. Add the following to the Docker provider block:
       db.vm.provider "docker" do |d|
         d.image="mysql"
         d.env = {
           :MYSQL_ROOT_PASSWORD => ""root",
           :MYSQL_DATABASE     => ""dockertest",
           :MYSQL_USER         => ""dockertest",
           :MYSQL_PASSWORD     => ""d0cker"
         }
         d.ports =["3306:3306"]
         d.remains_running = "true"
       end

    The environment variables (d.env) are taken from the documentation on the MySQL Docker image page (https://registry.hub.docker.com/_/mysql/). This is how the image expects to set certain parameters. In this case, our parameters will set the database root password (for the root user) and create a database with a new user that has full permissions to that database.

    The d.ports parameter is an array of port listings that will be forwarded from the container (the default MySQL port of 3306) to the host operating system, in this case also 3306.The contained application will, thus, behave like a natively installed MySQL installation.

    The port forwarding here is from the container to the operating system that hosts the container (in this case, the container host is our boot2docker image). If we are developing and hosting containers natively with Vagrant on a Linux distribution, the port forwarding will be to localhost, but boot2docker introduces something of a wrinkle in doing Docker development on Windows or OS X. We’ll either need to refer to our software installation by the IP of the boot2docker container or configure a second port forwarding configuration that allows a Docker contained application to be available to the host operating system as localhost.

    The final parameter (d.remains_running = true) is a flag for Vagrant to note that the Vagrant run should mark as failed if the Docker container exits on start. In the case of software that runs as a daemon process (such as the MySQL database), a Docker container that exits immediately is an error condition.

  3. Start the container using the vagrant up –provider=docker command. A few things will happen here:
    • If this is the first time you have started the project, you’ll see some messages about booting a box named mitchellh/boot2docker. This is a Vagrant-packaged version of the boot2docker project. Once the machine boots, it becomes a host for all Docker containers managed with Vagrant.

      Keep in mind that boot2doocker is necessary only for nonLinux operating systems that are running Docker through a virtual machine. On a Linux system running Docker natively, you will not see information about boot2docker.

    • After the container is booted (or if it is already running), Vagrant will display notifications about rsyncing a folder (if we are using boot2docker) and launching the image:

    Docker generates unique identifiers for containers and notes any port mapping information.

  4. Let’s take a look at some details on the containers that are running in the Docker host. We’ll need to find a way to gain access to the Vagrant boot2docker image (and only if we are using boot2docker and not a native Linux environment), which is not quite as straightforward as a vagrant ssh; we’ll need to identify the Vagrant container to access.

    First, identify the Docker Vagrant machine from the global Vagrant status. Vagrant keeps track of running instances that can be accessed from Vagrant itself. In this case, we are only interested in the Vagrant instance named docker-host. The instance we’re interested in can be found with the vagrant global-status command:

    In this case, Vagrant identifies the instance as d381331 (a unique value for every Vagrant machine launched). We can access this instance with a vagrant ssh command:

    vagrant ssh d381331

    This will display an ASCII-art boot2docker logo and a command prompt for the boot2docker instance. Let’s take a look at Docker containers running on the system with the docker psps command:

    The docker ps command will provide information about the running Docker containers on the system; in this case, the unique ID of the container (output during the Vagrant startup) and other information about the container.

  5. Find the IP address of the boot2docker (only if we’re using boot2docker) to connect to the MySQL instance. In this case, execute the ifconfig command:

    docker@boot2docker:~$ ifconfig

    This will output information about the network interfaces on the machine; we are interested in the eth0 entry. In particular, we can note the IP address of the machine on the eth0 interface:

    Make a note of the IP address noted as the inet addr; in this case 192.168.30.129.

  6. Connect a MySQL client to the running Docker container. In this case, we’ll need to note some information to the connection:
    • The IP address of the boot2docker virtual machine (if using boot2docker). In this case, we’ll note 192.168.30.129.
    • The port that the MySQL instance will respond to on the Docker host. In this case, the Docker container is forwarding port 3306 in the container to port 3306 on the host.
    • Information noted in the Vagrantfile for the username or password on the MySQL instance.

      With this information in hand, we can configure a MySQL client. The MySQL project provides a supported GUI client named MySQL Workbench (http://www.mysql.com/products/workbench/). With the client installed on our host operating system, we can create a new connection in the Workbench client (consult the documentation for your version of Workbench, or use a MySQL client of your choice).

      In this case, we’re connecting to the boot2docker instance. If you are running Docker natively on a Linux instance, the connection should simply forward to localhost. If the connection is successful, the Workbench client once connected will display an empty database:

    Once we’ve connected, we can use the MySQL database as we would for any other MySQL instance that is hosted this time in a Docker container without having to install and configure the MySQL package itself.

Building a Docker image with Vagrant

While launching packaged Docker, applications can be useful (particularly in the case where launching a Docker container is simpler than native installation steps), Vagrant becomes even more useful when used to launch containers that are being developed. On OS X and Windows machines, the use of Vagrant can make managing the container deployment somewhat simpler through the boot2docker containers, while on Linux, using the native Docker tools could be somewhat simpler. In this example, we’ll use a simple Dockerfile to modify a base image.

  1. First, start with a simple Vagrantfile. In this case, we’ll specify a build directory rather than a image file:
    # -*- mode: ruby -*-
    # vi: set ft=ruby :
     
    # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
    VAGRANTFILE_API_VERSION = "2"
    ENV['VAGRANT_DEFAULT_PROVIDER'] = 'vmware_fusion'
     
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
    config.vm.define "nginx" do |nginx|
       nginx.vm.provider "docker" do |d|
         d.build_dir = "build"
         d.ports = ["49153:80"]
       end
    end
    end

    This Vagrantfile specifies a build directory as well as the ports forwarded to the host from the container. In this case, the standard HTTP port (80) forwards to port 49153 on the host machine, which in this case is the boot2docker instance.

  2. Create our build directory in the same directory as the Vagrantfile.
  3. In the build directory, create a Dockerfile. A Dockerfile is a set of instructions on how to build a Docker container. See https://docs.docker.com/reference/builder/ or James Turnbull’s The Docker Book for more information on how to construct a Dockerfile. In this example, we’ll use a simple Dockerfile to copy a working HTML directory to a base NGINX image:
    FROM nginx
    COPY content /usr/share/nginx/html
  4. Create a directory in our build directory named content. In the directory, place a simple index.html file that will be served from the new container:
    <html>
    <body>
       <div style="text-align:center;padding-top:40px;border:dashed 2px;">
         This is an NGINX build.
       </div>
    </body>
    </html>

    Once all the pieces are in place, our working directory will have the following structure:

    .
    ├── Vagrantfile
    └── build
    ├── Dockerfile
       └── content
           └── index.html
  5. Start the container in the working directory with the command:

    vagrant up nginx –provider=docker

    This will start the container build and deploy process.

  6. Once the container is launched, the web server can be accessed using the IP address of the boot2docker instance (see the previous section for more information on obtaining this address) and the forwarded port.

One other item to note, especially, if you have completed both steps in this section without halting or destroying the Vagrant project is that when using the Docker provider, containers are deployed to a single shared virtual machine. If the boot2docker instance is accessed and the docker ps command is executed, it can be noted that two separate Vagrant projects deploy containers to a single host.

When using the Docker provider, the single instance has a few effects:

  • The single virtual machine can use fewer resources on your development workstation
  • Deploying and rebuilding containers is a process that is much faster than booting and shutting down entire operating systems

Docker development with the Docker provider can be a useful technique to create and test Docker containers, although Vagrant might not be of particular help in packaging and distributing Docker containers. If you wish to publish containers, consult the documentation or The Docker Book on getting started with packaging and distributing Docker containers.

See also

  • Docker: http://docker.io
  • boot2docker: http://boot2docker.io
  • The Docker Book: http://www.dockerbook.com
  • The Docker repository: https://registry.hub.docker.com

Summary

In this article, we learned how to use Docker provisioner with Vagrant by covering the topics mentioned in the preceding paragraphs.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here