Your First Vagrant Environment

30 minLesson 4 of 5

Learning Objectives

  • Create and understand a Vagrantfile
  • Deploy a container using vagrant up
  • Validate configurations with vagrant validate
  • Build a custom Docker image for Vagrant SSH access
  • Connect to containers via vagrant ssh

Creating a Project Directory

Every Vagrant environment lives in its own directory. Let's create one:

mkdir ~/vagrant-lab
cd ~/vagrant-lab

Initializing a Vagrant Environment

The vagrant init command creates a template Vagrantfile:

vagrant init hashicorp/bionic64

This creates a Vagrantfile with comments explaining each option. However, we want to use Docker — not VirtualBox. Let's replace the contents entirely.

Writing Your First Vagrantfile

Open the Vagrantfile and replace everything with:

ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
 
Vagrant.configure("2") do |config|
  config.vm.provider "docker" do |container|
    container.image = "nginx"
    container.ports = ["8080:80"]
    container.name = "my-first-container"
  end
 
  config.vm.synced_folder ".", "/vagrant"
end

Understanding Each Line

LineMeaning
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'Use Docker as the provider
`Vagrant.configure("2") doconfig
config.vm.provider "docker"Configure Docker-specific settings
container.image = "nginx"Use the nginx Docker image
container.ports = ["8080:80"]Map host port 8080 → container port 80
container.name = "my-first-container"Name the container
config.vm.synced_folder ".", "/vagrant"Sync current directory to /vagrant in container

Validating the Vagrantfile

Always validate before deploying:

vagrant validate

Expected output:

Vagrantfile validated successfully.

Deploying the Environment

vagrant up

Output:

Bringing machine 'default' up with 'docker' provider...
==> default: Creating and configuring docker networks...
==> default: Creating the container...
    default:   Name: my-first-container
    default:  Image: nginx
    default: Volume: /home/user/vagrant-lab:/vagrant
    default:   Port: 8080:80
    default:
    default: Container created: 46a172843afc
==> default: Starting container...

Vagrant pulled the nginx image and started a container. You can verify with:

vagrant status

Output:

Current machine states:

default                   running (docker)

Checking the Container

Since we mapped port 8080, you can test the nginx server:

curl http://localhost:8080

You should see the nginx welcome page HTML.

The SSH Problem

Try connecting:

vagrant ssh

This will fail because the standard nginx image doesn't include an SSH server. To use vagrant ssh, we need a custom image.

Building an SSH-Enabled Image

Create a Dockerfile in your project directory:

FROM ubuntu:focal
 
RUN apt-get update && \
    apt-get -y install openssh-server passwd sudo iproute2 \
    git curl iputils-ping net-tools wget && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 
# Enable systemd
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
    rm -f /lib/systemd/system/multi-user.target.wants/*; \
    rm -f /etc/systemd/system/*.wants/*; \
    rm -f /lib/systemd/system/local-fs.target.wants/*; \
    rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
    rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
    rm -f /lib/systemd/system/basic.target.wants/*; \
    rm -f /lib/systemd/system/anaconda.target.wants/*
 
RUN systemctl enable ssh.service
 
# Create vagrant user with SSH access
RUN useradd --create-home -s /bin/bash vagrant && \
    echo -e "vagrant\nvagrant" | (passwd --stdin vagrant) && \
    echo 'vagrant ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/vagrant && \
    chmod 440 /etc/sudoers.d/vagrant
 
# Set up SSH keys
RUN mkdir -p /home/vagrant/.ssh && \
    chmod 700 /home/vagrant/.ssh
ADD https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant.pub /home/vagrant/.ssh/authorized_keys
RUN chmod 600 /home/vagrant/.ssh/authorized_keys && \
    chown -R vagrant:vagrant /home/vagrant/.ssh
 
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]

What This Dockerfile Does

  1. Base image — Ubuntu 20.04 (Focal)
  2. Installs SSH — OpenSSH server for remote access
  3. Enables systemd — Service management inside the container
  4. Creates vagrant user — With sudo privileges, no password required
  5. Configures SSH keys — Uses Vagrant's insecure keypair for initial access
  6. Mounts cgroup — Required for systemd to function

Building the Image

docker build -t nextgenplayground:vagrant .

Updating the Vagrantfile

Now destroy the old container and update the Vagrantfile:

vagrant destroy -f

Replace the Vagrantfile contents:

ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
 
Vagrant.configure("2") do |config|
  config.vm.define "lab" do |lab|
    lab.vm.network "forwarded_port", guest: 80, host: 8080
    lab.vm.provider "docker" do |docker|
      docker.image = "nextgenplayground:vagrant"
      docker.has_ssh = true
      docker.privileged = true
      docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro"]
      docker.name = "lab"
    end
  end
end

Key Differences

SettingPurpose
docker.has_ssh = trueTells Vagrant SSH is available
docker.privileged = trueAllows systemd to run
docker.create_argsMounts cgroup volume

Deploying and Connecting

vagrant up

Output:

==> lab: Creating the container...
==> lab: Starting container...
==> lab: Waiting for machine to boot...
    lab: SSH address: 127.0.0.1:2222
    lab: SSH username: vagrant
    lab: Vagrant insecure key detected. Vagrant will automatically replace
    lab: this with a newly generated keypair for better security.
==> lab: Machine booted and ready!

Now connect:

vagrant ssh

Output:

Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.x.x x86_64)
vagrant@lab:~$

You're inside the container! Run any command:

whoami        # vagrant
hostname      # lab
ip addr       # see network interfaces
exit          # return to host

Vagrant Lifecycle Commands

# Start environment
vagrant up
 
# Connect via SSH
vagrant ssh
 
# Stop (preserve state)
vagrant halt
 
# Restart (reload config)
vagrant reload
 
# Delete everything
vagrant destroy -f
 
# Check status
vagrant status
vagrant global-status

Summary

  • A Vagrantfile describes your environment in Ruby syntax
  • vagrant validate checks for configuration errors
  • vagrant up creates and starts the environment
  • Standard Docker images don't support SSH — you need a custom image
  • The custom Dockerfile adds SSH, systemd, and the vagrant user
  • vagrant ssh connects you to the running container
  • vagrant destroy -f removes everything cleanly

Next Steps

Now that you can create and connect to environments, let's explore networking — port forwarding, private networks, and multi-container architectures.