How to Set Up a Local Docker Host by Using Vagrant: Development Environment Configuration Guide

How T Set Up a Local Docker Host by Using Vagrant

Long before Docker Desktop existed for Mac and Windows, running Docker meant running Linux, and the easiest way to get a consistent Linux VM on any host operating system was Vagrant. Even today, I still reach for Vagrant when I want a fully isolated, reproducible Docker host that’s separate from my actual laptop — useful for testing daemon configuration changes, experimenting with different Docker versions side by side, or giving a team a byte-for-byte identical environment regardless of whether they’re on macOS, Windows, or Linux.

What Vagrant Actually Provides

Vagrant is a tool for describing and provisioning virtual machines declaratively, using a Vagrantfile. It sits on top of a virtualization provider — VirtualBox, VMware, Hyper-V, or libvirt — and automates VM creation, networking, shared folders, and provisioning scripts. For this guide I’ll use VirtualBox, since it’s free and cross-platform.

Prerequisites

  • VirtualBox installed
  • Vagrant installed (vagrant --version)
  • About 2GB of free disk space for the base box
vagrant --version
Vagrant 2.4.1

Step 1: Create a Project Directory

mkdir docker-vagrant-host && cd docker-vagrant-host

Step 2: Initialize a Vagrantfile

vagrant init ubuntu/jammy64
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

Step 3: Edit the Vagrantfile to Provision Docker Automatically

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/jammy64"
  config.vm.hostname = "docker-host"

  config.vm.network "private_network", ip: "192.168.56.10"
  config.vm.network "forwarded_port", guest: 80, host: 8080

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "4096"
    vb.cpus = 2
  end

  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get install -y ca-certificates curl gnupg

    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc

    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
      https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
      tee /etc/apt/sources.list.d/docker.list > /dev/null

    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    usermod -aG docker vagrant
  SHELL
end

A few decisions worth explaining:

  • private_network with a static IP gives me a predictable address to reach the VM from my host machine, separate from NAT port forwarding.
  • forwarded_port maps a specific container-facing port (80) to a host-facing one (8080), so I can hit localhost:8080 from my actual laptop browser.
  • The inline shell provisioner runs Docker’s official APT-based installation, identical to installing Docker on a bare Ubuntu server.
  • usermod -aG docker vagrant lets the default vagrant user run Docker commands without sudo.

Step 4: Bring Up the VM

vagrant up

Expected output (abbreviated):

Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/jammy64'...
==> default: Setting the name of the VM: docker-vagrant-host_default
==> default: Configuring and enabling network interfaces...
==> default: Running provisioner: shell...
    default: Running: inline script
    default: Reading package lists...
    default: Setting up docker-ce (5:27.3.1-1~ubuntu.22.04~jammy) ...
    default: Setting up docker-ce-cli (5:27.3.1-1~ubuntu.22.04~jammy) ...

The full provisioning run typically takes three to six minutes depending on connection speed.

Step 5: SSH Into the VM

vagrant ssh
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-101-generic x86_64)
vagrant@docker-host:~$

Verify Docker:

docker --version
docker run hello-world
Docker version 27.3.1, build ce12230
...
Hello from Docker!
This message shows that your installation appears to be working correctly.

Step 6: Run a Real Container and Reach It From the Host

Inside the VM:

docker run -d -p 80:80 --name vm-nginx nginx:latest

From my actual laptop (outside the VM):

curl http://localhost:8080
<!DOCTYPE html>
<html>
<head><title>Welcome to nginx!</title></head>
...

The forwarded port I defined in the Vagrantfile makes port 80 inside the VM reachable at port 8080 on my host machine.

Step 7: Shared Folders for Development

By default, Vagrant syncs the project directory (where the Vagrantfile lives) into /vagrant inside the VM — handy for editing Dockerfiles and Compose files on my host with my normal editor, while building and running them inside the VM:

vagrant ssh -c "ls /vagrant"
Vagrantfile

Step 8: Managing the VM Lifecycle

vagrant halt      # gracefully shuts down the VM
vagrant suspend    # saves state and pauses, faster to resume
vagrant resume     # resumes a suspended VM
vagrant destroy    # deletes the VM entirely
vagrant destroy -f
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives...

Internal Working: What Vagrant Is Doing Underneath

Vagrant itself doesn’t run VMs — it’s an orchestration layer over a provider (VirtualBox here). When I run vagrant up, Vagrant reads the Vagrantfile, calls the VirtualBox provider driver to import the specified base box (a pre-built VM disk image plus metadata), configures the VM’s virtual NICs and forwarded ports via VirtualBox’s own CLI (VBoxManage), boots it, waits for SSH to become reachable, then executes the shell provisioner script over that SSH connection exactly as if I’d typed the commands myself. The Docker installation itself is entirely standard — Vagrant contributes nothing Docker-specific; it’s simply automating “boot a fresh Ubuntu VM and run these exact commands on it.”

Networking Considerations

Two networking modes are active simultaneously here: the default NAT interface (which is how vagrant ssh reaches the VM, and how the VM reaches the internet to install packages), and the private_network interface I added, which puts the VM on an isolated host-only network at a fixed IP. Port forwarding is a third, independent mechanism layered on top of the NAT interface — it’s why localhost:8080 on my host reaches port 80 inside the VM without needing the private network IP at all.

Storage Considerations

The VM’s virtual disk is separate from my host filesystem entirely, except for the synced /vagrant folder. Docker images and containers pulled/created inside the VM live on that virtual disk, which means destroying the VM (vagrant destroy) wipes all pulled images and container data — intentional for a disposable dev environment, but worth remembering if I’ve built something I want to keep.

Security Considerations

  • Vagrant’s default SSH setup uses an insecure, publicly known private key for the initial box unless explicitly replaced — fine for a local dev VM isolated from the internet, but never expose this VM’s SSH port externally without hardening it first.
  • The private_network IP I configured is only reachable from my host machine’s network stack by default, not the wider internet, but I still avoid running anything sensitive on it long-term.
  • Since the vagrant user is added to the docker group, any process running as that user has effectively root-equivalent control over the VM (a general property of Docker group membership, not specific to Vagrant).

Troubleshooting

“VT-x is not available” or virtualization errors Usually means hardware virtualization isn’t enabled in the host machine’s BIOS, or it’s conflicting with another hypervisor (like Hyper-V on Windows) that’s already claimed it.

Provisioning script fails partway through Re-run just the provisioner without recreating the whole VM:

vagrant provision

Forwarded port already in use on the host

Vagrant cannot forward the specified ports on this VM, since they
would collide with some other application that is already listening
on these ports.

Change the host-side port number in the Vagrantfile’s forwarded_port line and run vagrant reload.

Monitoring

vagrant ssh -c "docker stats --no-stream"
vagrant global-status
id       name    provider   state   directory
a1b2c3d  default virtualbox running /home/user/docker-vagrant-host

Best Practices

  • Pin the base box version explicitly (config.vm.box_version) for reproducibility across teammates.
  • Keep provisioning scripts idempotent so vagrant provision can be re-run safely.
  • Use private_network with static IPs for predictable access rather than relying solely on NAT/forwarded ports.
  • Destroy and recreate VMs regularly rather than letting configuration drift accumulate.

Summary

Vagrant gives me a fully reproducible, disposable Linux VM with Docker installed via a single declarative file and one command, independent of whatever operating system my actual laptop runs. It’s an excellent way to test Docker daemon configurations, try different Docker versions, or hand a team an identical environment — all without touching my host machine’s own software directly.

References

  • Vagrant documentation: https://developer.hashicorp.com/vagrant/docs
  • VirtualBox documentation: https://www.virtualbox.org/wiki/Documentation
  • Docker Engine installation on Ubuntu: https://docs.docker.com/engine/install/ubuntu/
  • Vagrant Cloud (base box registry): https://app.vagrantup.com/boxes/search
Total
0
Shares

Leave a Reply

Previous Post
how to Install Docker on a Raspberry Pi

How to Install Docker on a Raspberry Pi: Complete Step-by-Step Setup Guide for ARM Devices

Next Post
How to Start a Docker Host in the Cloud by Using Docker Machine

How to Start a Docker Host in the Cloud by Using Docker Machine: Complete Provisioning Guide

Related Posts