In our last guide, we looked at how to install and use Vagrant on Ubuntu. We defined what Vagrant is and how developers benefit in easily creating virtual environments both for tests and productions. We also said that Vagrant runs virtual machines on top of a hypervisor or a provider such as VMware, Hyper-V, Docker, KVM and AWS. Virtualbox is the default provider for Vagrant.

In this guide, we are going to look at how to run docker containers using Vagrant. Just like Virtualbox, docker can also be used as Vagrant provider to run docker containers. We still require Vagrantfile to define the container specifications. The Vagrantfile can be defined to use an image that will be pulled for a docker registry or defined to use a local Dockerfile to build an image and run a container. As such the Docker provider does not require a config.vm.box setting.

You should already have Vagrant and docker installed on your system to be able to run docker containers with Vagrant.

Docker Installation guides:

How To Install and Use Docker CE in Ubuntu | Linux Mint

How To Install Docker CE on Amazon Linux 2

How To Install Docker CE on Manjaro Linux

Vagrant installation guides:

How To Install and Use Vagrant on Ubuntu

How To Install Vagrant on Oracle Linux

Using Docker Images with Vagrant

Here, the Vagrantfile is configure to create a container using an image that is available in a docker registry. If you do not have the image already in your working directory, the image will be downloaded and container started. Check the simple example below:

$ vim Vagrantfile

Add the below content and save the file

Vagrant.configure("2") do |config|
  config.vm.provider "docker" do |d|
    d.image = "nginx:latest"
    d.ports = [“8080:80”]
    d.name = “nginx-container”
  end
end

Start the container

$ vagrant up
Bringing machine 'default' up with 'docker' provider...
==> default: Creating and configuring docker networks...
==> default: Fixed port collision for 22 => 2222. Now on port 2201.
==> default: Creating the container...
    default:   Name: nginx-container
    default:  Image: nginx:latest
    default: Volume: /home/lorna/Vagrant/docker/nginx:/vagrant
    default:   Port: 8080:80
    default:  
    default: Container created: 820e5a9aec2d6e19
==> default: Enabling network interfaces...
==> default: Starting container...

Confirm running containers:

$ docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
820e5a9aec2d        nginx:latest                   "/docker-entrypoint.…"   40 seconds ago      Up 38 seconds       0.0.0.0:80->80/tcp       nginx-container

If you head over to your browser on <your-host-ip>:8080, you should see the default Nginx welcome page

Using Dockerfiles with Vagrant

If you are building your own docker images, you can still use Vagrant to run your containers. The only thing you do is to specify the path to your Dockerfile inside Vagrantfile. The best way is to have Dockerfile in the same directory as Vagrantfile. Check the example below:

Vagrant.configure("2") do |config|
  config.vm.provider "docker" do |d|
    d.build_dir = "."
  end
end

When you run ‘vagrant up’ the configuration checks for Dockerfile in the same directory as Vagrantfile. It then automatically builds the image from the Dockerfile and runs the container.

Vagrant-Docker Commands

These are the commands used to manipulate the containers created using Vagrant. Let us have a look at some of them using the Nginx container created above

vagrant docker-exec

Is used to run one-off commands against a currently running Docker container.

$ vagrant docker-exec -it default -- /bin/sh
# ls
bin  boot  dev	docker-entrypoint.d  docker-entrypoint.sh  etc	home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  vagrant  var
# 

Note that the name ‘default’ refers to the name of the first defined VM. You can get the name by checking vagrant status

$ vagrant status
Current machine states:

default                   running (docker)

The container is created and running. You can stop it using
`vagrant halt`, see logs with `vagrant docker-logs`, and
kill/destroy it with `vagrant destroy`.

If your Vagrantfile defines multiple containers, the name corresponds to the name of the Virtual Machine and NOT the name of the container. Check the example below:

Vagrant.configure do |config|
  config.vm.define "app" do
    config.vm.provider "docker" do |d|
      d.image = "nginx"
    end
  end

  config.vm.define "consul" do
    config.vm.provider "docker" do |d|
      d.image = "consul"
    end
  end
end

In this case, to enter Nginx container, we run the command below:

vagrant docker-exec -it app -- /bin/sh

vagrant halt

The command is used to stop the running container

$ vagrant halt
==> default: Stopping container...

vagrant destroy

The command destroys all the resources created during the entire process:

$ vagrant destroy

vagrant docker-logs

Used for checking Vagrant logs

$ vagrant docker-logs
==> default: /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
==> default: /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
==> default: /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
==> default: 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
==> default: 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
==> default: /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
==> default: /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
==> default: /docker-entrypoint.sh: Configuration complete; ready for start up
==> default: 172.17.0.1 - - [20/Mar/2021:10:57:09 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36" "-"

Networking

Vagrant makes use of docker network for creating and managing networks for the containers configured within a Vagrantfile. To create a new network, vagrant uses the command docker network create with the provided network configuration options within the Vagrantfile, each docker network grouped by the subnet used for a requested IP address and the networks are named as vagrant_network or vagrant_network_<subnet here>. When containers are destroyed, Vagrant will also clean up the networks once there are no more containers attached to them.

Docker Network Options

You can either specify an IP address or use dhcp to get network for your containers

docker.vm.network :private_network, type: "dhcp"
docker.vm.network :private_network, ip: "192.168.50.2"

New networks require netmask to be specified otherwise Vagrant will allocate a /24 for IPv4 and a /64 for IPv6. You can specify netmask as below:

docker.vm.network :private_network, ip: "192.168.50.2", netmask: 16

Even if you set the type to dhcp, you can still specify the subnet so that the containers do not connect to the default Vagrant network. You can specify in two ways as below

docker.vm.network :private_network, type: "dhcp", ip: "192.168.50.0", netmask: 24

or

docker.vm.network :private_network, type: "dhcp", subnet: "192.168.50.0/24"

Vagrant and Public Networks

The easiest way to define public container networks with Vagrant is by setting type to dhcp. A bridge interface is required and you can provide the available interfaces and Vagrant will use the first active interface.

docker.vm.network :public_network, type: "dhcp"
docker.vm.network :public_network, type: "dhcp", bridge: "eth0"
docker.vm.network :public_network, type: "dhcp", bridge: ["eth0", "wlan0"]

You can define a range of IP addresses for the containers instead of leaving it to use the set DHCP for the public network. The defined subnet should then be isolated from the public dhcp pool. For the gateway, Vagrant will use the default gateway available for the bridge interface subnet but cal also be set manually in Vagrantfile.

docker.vm.network :public_network, type: "dhcp", bridge: "eth0", docker_network__ip_range: "192.168.50.252/30"
docker.vm.network :public_network, type: "dhcp", bridge: "eth0", docker_network__gateway: "192.168.50.1"

Check the example below which creates the below networks for a container:

  • An IPv4 IP address via DHCP
  • A IPv4 IP address 192.168.50.4 on a network with subnet 192.168.50.0/24
  • A IPv6 IP address via DHCP on subnet 2a02:6b8:b010:9020:1::/80
Vagrant.configure("2") do |config|
  config.vm.define "docker"  do |docker|
    docker.vm.network :private_network, type: "dhcp", docker_network__internal: true
    docker.vm.network :private_network,
        ip: "192.168.50.4", netmask: "24"
    docker.vm.network :private_network, type: "dhcp", subnet: "2a02:6b8:b010:9020:1::/80"
    docker.vm.provider "docker" do |d|
      d.build_dir = "docker_build_dir"
    end
  end
end

This guide is meant to help you to get started on how to run and manage Docker containers using Vagrant. There is a lot more information especially is you are a developer and looking at managing a number of containers with Vagrant. I hope the guide has been useful in bringing you up to speed on running Docker with Vagrant. Check below more related guides which might be of interest to you: