In our last guide, we looked at the steps of installing and using 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. You can easily install docker using the following script.
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh
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 in Vagrant
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.