Lúc 2 giờ sáng và môi trường dev đang bốc khói
2:17 sáng. Deploy lên staging xong, mọi thứ chạy ngon. Sáng hôm sau developer gửi bug report: “Trên máy mình không reproduce được.” Mình ngồi nhìn màn hình 5 phút rồi hiểu ra vấn đề — anh dev đang chạy Ubuntu 20.04, server chạy Ubuntu 22.04, còn mình đang ngồi debug trên macOS. Ba môi trường khác nhau, một codebase.
Vagrant fix đúng cái đau đó. Không phải vì nó “tốt nhất” hay “hiện đại nhất” — mà vì một lý do đơn giản: một Vagrantfile commit vào git, developer nào clone về cũng vagrant up là có môi trường giống nhau, dù đang ngồi trên Mac, Windows hay Ubuntu.
So sánh các approach: VM thủ công, Docker, và Vagrant
Ba tool, ba triết lý thiết kế khác nhau. Hiểu đúng từng cái trước khi chọn — chọn sai tool cho bài toán không phải lần đầu mình bị:
Approach 1: VM thủ công (VirtualBox/VMware)
Tạo VM bằng tay, cài OS, setup dependencies, snapshot lại. Cách này mình đã làm trong homelab — hiện tại có Proxmox VE quản lý 12 VM và container, dùng như playground để test mọi thứ trước khi đưa lên production. Với homelab thì ổn, nhưng với team dev thì đây là cơn ác mộng.
- Không thể share config dưới dạng code
- Mỗi dev tự setup tay → không nhất quán
- Onboarding member mới mất cả ngày
- Không version control được môi trường
Approach 2: Docker + Docker Compose
Container-based, lightweight, portable. Đây là approach mình dùng nhiều nhất cho microservices. Nhưng Docker có điểm yếu khi cần môi trường “giống server thật” hơn:
- Container share kernel với host — không phải full OS isolation
- Một số tool yêu cầu systemd, kernel module, hoặc cần test network stack thật
- Dev chạy Docker Desktop trên Mac/Windows có overhead và behavior khác Linux native — từng mất 2 tiếng debug bug permission chỉ reproduce được trên Linux do cách xử lý bind mount khác nhau, file 777 trên Mac không ảnh hưởng gì cả
- Không phù hợp khi cần test cấu hình firewall, disk partitioning, hay kernel tuning
Approach 3: Vagrant
Vagrant wrap lại việc tạo và quản lý VM bằng một file config duy nhất — Vagrantfile. Không phải thay thế Docker, mà bổ sung cho nó trong những case cần full VM.
- Vagrantfile commit vào git → môi trường là code, có version history
- Full VM isolation, có thể test kernel-level stuff
- Hỗ trợ nhiều provider: VirtualBox, VMware, Hyper-V, libvirt
- Có thể chạy Docker bên trong Vagrant VM
Phân tích ưu nhược điểm của Vagrant
Ưu điểm
- Infrastructure as Code: Vagrantfile là Ruby DSL, dễ đọc, dễ edit, commit được vào git
- Reproducible:
vagrant destroy && vagrant uplà bạn có môi trường fresh như mới - Multi-machine: Định nghĩa nhiều VM trong một Vagrantfile (web + db + cache)
- Provisioning tích hợp: Shell script, Ansible, Chef, Puppet đều được hỗ trợ sẵn
- Shared folders: Code trên host, chạy trong VM — edit bằng IDE quen thuộc mà không cần copy file
Nhược điểm
- Nặng hơn Docker: Full VM tốn RAM và disk nhiều hơn container đáng kể
- Boot time chậm: Khởi động VM mất 30–60 giây, container chỉ vài giây
- Overhead hypervisor: Cần VirtualBox (mặc định) — có thể conflict với Docker Desktop trên Windows do tranh chấp Hyper-V
- Ít phù hợp với microservices: Nếu project có 20 services, Docker Compose hợp lý hơn nhiều
Vagrant hay Docker: Khi nào dùng cái nào?
Rule đơn giản của mình sau nhiều lần chọn sai tool:
- Dùng Docker khi: app là web service, microservices, cần spin up/down nhanh, CI/CD pipeline
- Dùng Vagrant khi: cần test system-level stuff, simulate multi-server setup, team có người dùng Mac/Windows nhưng server là Linux, hoặc cần môi trường giống production 100% về kernel và network
Use case thực tế mình hay dùng Vagrant nhất: test Ansible playbook trước khi apply lên production, simulate network topology với nhiều VM, và reproduce bug chỉ xảy ra trên kernel version cụ thể.
Hướng dẫn triển khai Vagrant từ đầu
Bước 1: Cài đặt
Vagrant cần một provider VM. Mặc định dùng VirtualBox:
# Ubuntu/Debian — cài VirtualBox
sudo apt update && sudo apt install -y virtualbox
# Cài Vagrant từ HashiCorp official repo
wget -O- https://apt.releases.hashicorp.com/gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vagrant
# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/vagrant
# Kiểm tra cài đặt
vagrant --version
# Vagrant 2.4.x
Bước 2: Tạo Vagrantfile đầu tiên
Tạo thư mục project và viết Vagrantfile. Mình thường tự viết thay vì dùng vagrant init — file sinh ra đầy comment, đọc xong mất cả buổi. Tự viết từ đầu ngắn hơn và rõ hơn nhiều. Đây là config cho LAMP stack đơn giản:
Vagrant.configure("2") do |config|
# Base box — Ubuntu 22.04 LTS
config.vm.box = "ubuntu/jammy64"
config.vm.box_check_update = false
# Forward port 80 và 3306 ra host
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 3306, host: 3306
# Private network với IP tĩnh (dễ nhớ hơn)
config.vm.network "private_network", ip: "192.168.56.10"
# Shared folder: code trên host mount vào /var/www trong VM
config.vm.synced_folder "./src", "/var/www/html"
# VM resources
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
vb.cpus = 2
vb.name = "dev-lamp"
end
# Provisioning: chạy shell script khi tạo VM lần đầu
config.vm.provision "shell", inline: <<-SHELL
apt-get update -q
apt-get install -y -q apache2 mysql-server php php-mysql
systemctl enable apache2 mysql
systemctl start apache2 mysql
echo "Done! Truy cập: http://192.168.56.10"
SHELL
end
Bước 3: Khởi động và SSH vào VM
# Tạo và start VM (lần đầu download box ~500MB)
vagrant up
# SSH vào VM
vagrant ssh
# Kiểm tra trong VM
uname -a
# Linux ubuntu-jammy 5.15.0-xx-generic ...
# Thoát
exit
Bước 4: Workflow hàng ngày
# Bật VM (đã tạo sẵn, nhanh hơn lần đầu)
vagrant up
# Tạm dừng — giữ nguyên state RAM (nhanh nhất)
vagrant suspend
vagrant resume
# Tắt VM hẳn (giải phóng RAM)
vagrant halt
# Xóa VM hoàn toàn và tạo lại từ đầu
vagrant destroy -f && vagrant up
Bước 5: Multi-machine — simulate kiến trúc production
Đây là điểm Vagrant thật sự nổi bật so với setup tay. Simulate web server + database server tách biệt, đúng như production:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/jammy64"
# Web server
config.vm.define "web" do |web|
web.vm.hostname = "web-server"
web.vm.network "private_network", ip: "192.168.56.10"
web.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
web.vm.provision "shell", inline: "apt-get install -y -q nginx"
end
# Database server
config.vm.define "db" do |db|
db.vm.hostname = "db-server"
db.vm.network "private_network", ip: "192.168.56.11"
db.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
end
db.vm.provision "shell", inline: "apt-get install -y -q postgresql"
end
end
# Start tất cả machines
vagrant up
# Start chỉ web server
vagrant up web
# SSH vào từng machine
vagrant ssh web
vagrant ssh db
Tip: Dùng Ansible provisioner thay vì shell inline
Shell script inline ổn để test nhanh. Với project nghiêm túc hơn, mình chuyển sang Ansible provisioner:
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provision/setup.yml"
ansible.verbose = "v"
end
Lợi ích kép: cùng playbook đó có thể apply thẳng lên production server thật — đúng nghĩa “dev environment giống production đến từng dòng config”.
Kết luận thực tế
Quan điểm của mình sau nhiều lần debug lúc 2 giờ sáng vì môi trường không nhất quán: không cần chọn một trong hai. Vagrant chạy VM, Docker chạy container bên trong VM đó — bộ đôi này cover được hầu hết use case.
Cần reproduce bug “chỉ xảy ra trên server”? Muốn onboard dev mới mà không mất nửa ngày setup? Cần test Ansible playbook trước khi apply lên production? Vagrantfile trong git repo là câu trả lời. vagrant up là xong.

