Previous Next

How to setup a dockerized vm using Vagrant

When I started to experiment with docker, I had to run a debian system in a virtual OS as I primarily use windows for my everyday development and if you look at the development setup of our team you'll find a pretty heterogenous landscape with respect to the used OS's or IDE's. So in general, sharing (infrastructure) code for external resources and services is one thing we do to strive against this kind of fragmentation. 

Everyone can set up the required system easily and repeatable.

While Virtualbox allows me to install a clean new debian/ubuntu with just a couple of mouse clicks, it's slightly more complicated to manage all required resources across both systems, which include folder synchronizations and network settings between the host and the guest system. The setup of docker is still missing, but fortunately, docker's documentation is quite helpful and the installation process, as well as an automatic management through systemd, will guide to a running system in just a couple of quarters.

With all that done I had a running debian with docker properly installed, but was missing a feature that I personally like pretty much: An integration into the IDE and this is where IntelliJ comes into play. IntelliJ Idea supports this feature since version 14.1 and the following steps need to be accomplished

  1. Configure Virtualbox's network to allow access from your host system to GUESTIP:PORT
  2. Bind docker's API to the accessible GUESTIP of the guest system using the following daemon argument -H=tcp://GUESTIP:PORT where PORT can be freely chosen, e.g. -H tcp://192.168.33.11:2375
  3. Configure Idea's plugin to use the docker's API using the following scheme: http://GUESTIP:PORT, e.g. http://192.168.33.11:2375

If you expose docker's API to the network, you may encounter the following statements in the log files or journal

"! DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING !"
"Listening for HTTP on tcp (192.168.33.11:2375)"

You should think twice if you want to use the same settings for your production system and care should be taken if you plan to do.

As time goes on and the system state of the guest system 'evolved' :) simultaneously - sometimes a bit more into a trashy direction than I wanted - it became harder to manage and I felt that I should just bring up a clean system again. It was eligible to gain more knowledge and insights and to make some experiments and learn something new, but it wasn't sustainable and pretty much error-prone due to all the manual working steps.
Further, a graphical interface didn't provide any benefit to me, but rather a more visual distraction and other procedures that took my focus, which could be better replaced by a dockerized system that runs headless in the background.

All that sounds like a recipe for infrastructure again.

A colleague of mine told me about Vagrant and how they use it in their projects. So back at home, I started with a simple Vagrantfile to model the recipe from my working steps. I'm not a ruby expert - programming language behind it - and I googled quite a lot to find out how people write ruby code.

You can find the full Vagrantfile as a Gist and start playing around and before I start with some explanation of selected code parts I'd like to continue the setup if you plan to try it out first.

  • VirtualBox - 5.0
  • Guest Additions - 4.3.18
  • Winscp - 5.7.5
  • Putty - 0.6.3
  • Vagrant - 1.7.4

Ok, so here we go. Please take a look at the inlined comments.

# The provisioning script has been tested with a debian image and systemd is the only requirement. Any other distribution should work as long as it's supported by the docker installation script.

name          = "encodeering"
image         = "debian/jessie64"
cpu           = 2
memory        = 2048
ip            = "192.168.33.11"
port          = 2375

# Exposing the API with the resulting challenges and shrinking the network size from /16 to /24

dockeroption  = "-H=tcp://#{ip}:#{port} --bip 172.17.42.1/24"

# Define your network constraints here following a HOST => GUEST semantic, either using with a stringified notation (no configuration) or hash-symbol notation (with configuration)
# All of Vagrant's network parameters can be used. The string (:to) will be used to serve a few of them already, like :host_ip, :host, ...
#
# localhost:8080, 192.168.42.42:8080/tcp, 8080/udp, 8080, ...

forwardings   =  {
   "8080/tcp" =>          "80",
   "8080/udp" => { :to => "80" }
}

# Define your folder constraints here following a HOST => GUEST semantic, either using with a stringified notation (no configuration) or hash-symbol notation (with configuration)
# All of Vagrant's folder-share parameters can be used. The current example disables Vagrant's default mapping of the CWD directory

sharings      =  {
   "."        => { :to => "/vagrant", :disabled => true }
}

warning       = "#auto-generated file"
Vagrant.configure(2) do |config|

    config.ssh.insert_key = false
    
    # DNS will be taken from the HOST system, CPU cap is 75%
	
    config.vm.provider "virtualbox" do |vb|
        vb.gui    = false
        vb.name   = "#{normalize (name)}-#{normalize (image)}"  
        vb.cpus   = "#{cpu}"
        vb.memory = "#{memory}"
        vb.customize ["modifyvm", :id, "--cpuexecutioncap",     "75"]
        vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
    end
    config.vm.define name do |docker|
        docker.vm.box      = image
        docker.vm.hostname = normalize (name)
        # Iterating over all network mappings to create corresponding port mappings. 
        # You can find the related regex here as well to split the string into named arguments.
		
        forwardings.each_pair { |h, g|
                 args =             g = hashify g.clone, :to 
            to = args.delete (:to)
            
            #  e.g localhost:8080, 192.168.42.42:8080/tcp, 8080/udp, 8080, ...
            net = /^((?[^:]+):)?(?\d+)(\/(?\w+))?$/
        
            host  = net.match (h);
            guest = net.match (to);
            
            args[:host_ip]  = host[:ip]
            args[:host]     = host[:port]
            args[:protocol] = host[:protocol]
            args[:guest_ip] = guest[:ip]
            args[:guest]    = guest[:port]
            
            docker.vm.network "forwarded_port", args
        }
        
        docker.vm.network "private_network", ip: ip
        # Iterating over all folder mappings to create corresponding bindings.
		
        sharings.each_pair { |h, g|
                 args =          g = hashify g.clone, :to
            to = args.delete (:to)
            
            config.vm.synced_folder h, to, args
        }
        # A simple inline script to properly provision the box with docker. Systemd takes care of the rest later.
        # We clone the /lib systemd description to /etc and do NOT alter the original file so that we can then re-provision the box as often as we want to keep everything up to date.
		
        docker.vm.provision "shell", inline: <                          /etc/docker/config
            sudo echo "OPTIONS=#{dockeroption}" >>                         /etc/docker/config
            sudo cp  -f /lib/systemd/system/docker.service                 /etc/systemd/system/docker.service      
            sudo sed -i '1s/^/#{warning}\\n/'                              /etc/systemd/system/docker.service
            sudo sed -i '/ExecStart/i\EnvironmentFile=-/etc/docker/config' /etc/systemd/system/docker.service
            sudo sed -i '/ExecStart/ s/$/ $OPTIONS/'                       /etc/systemd/system/docker.service
            sudo systemctl reenable docker
            sudo systemctl restart  docker
        SHELL
    end
    
end

def normalize (text)
    return     text.gsub(/[^0-9A-Za-z]/, '-')
end

# Lifting anything to a hash for API homogeneity

def hashify (any, symbol)
    case any
        when Hash then return             any
        else           return { symbol => any }
    end
end

Feel free to comment and to share your thoughts. You may also want to read about docker-machine, which creates a docker host inside Virtualbox as well.

While vagrant ssh is working to enter the box, I had some problems with the windows shells and back-buffers and switched to a Putty then. The following commands should do it:

vagrant ssh-config
putty -ssh 127.0.0.1 -P 2222 -l vagrant -pw vagrant
putty -load an-earlier-created-profile -pw vagrant


Become a backer or share this article with your colleagues and friends. Any kind of interaction is appreciated.

Quick Link