Hướng dẫn dùng Vagrant để tạo môi trường dev: Chấm dứt cảnh ‘works on my machine’

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

Cái bẫy “works on my machine” và câu chuyện thực tế

Đã bao nhiêu lần bạn nghe câu “trên máy tao chạy ngon mà” khi deploy lên staging bị lỗi? Mình quen mặt cái cảnh này rồi. Một developer dùng Ubuntu 20.04, người khác dùng macOS Ventura, sếp chạy Windows 11 — tất nhiên mỗi người cài Node.js một phiên bản khác nhau, MySQL config khác nhau, Python path thì thôi khỏi nói.

Cái giá phải trả rất rõ ràng: thành viên mới join team mất 2–3 ngày chỉ để setup môi trường, chưa viết được dòng code nào. Bug xuất hiện, không ai chắc đó do code hay do môi trường. Debug mất thêm vài tiếng chỉ để phát hiện ra khác phiên bản một package. Developer không lười — đây là vấn đề cấu trúc mà không ai chủ động giải quyết.

Tại sao môi trường dev cứ bị “drift”?

Bóc tách ra thì có mấy nguyên nhân cốt lõi:

  • Mỗi người dùng OS khác nhau — macOS, Windows, Ubuntu — hành vi của cùng một lệnh đôi khi khác nhau hoàn toàn, đặc biệt liên quan đến file permission hay line ending
  • Không có cơ chế lock phiên bản hệ thốngpackage.json lock được Node packages, nhưng ai lock phiên bản OS, phiên bản OpenSSL hay glibc?
  • Setup thủ công từ README — README cũ, thiếu bước, hoặc người viết quên document một dependency cài từ 6 tháng trước
  • Accumulated drift — mỗi developer tự update máy theo lịch riêng, dần dần môi trường các máy diverge, và không ai nhận ra cho đến khi có bug lạ

Mình chạy homelab với Proxmox VE quản lý 12 VM và container — đây là playground để test mọi thứ trước khi đưa lên production. Chính từ việc quản lý nhiều VM như vậy mình mới nhận ra: tạo VM thủ công không scale được, phải tự động hóa toàn bộ quá trình.

Các cách giải quyết: từ thủ công đến tự động

Cách 1: VM thủ công + snapshot

Tạo một VM “chuẩn”, snapshot lại, clone cho mỗi developer. Nghe có vẻ ổn, nhưng thực tế file snapshot nặng hàng GB, không commit vào git được. Khi cần thêm dependency mới, phải thông báo tất cả mọi người update thủ công. Quan trọng hơn: không ai biết “chuẩn” nghĩa là cài gì, theo thứ tự nào — không reproducible.

Cách 2: Docker

Docker đóng gói app cực tốt — đây là use case nó sinh ra để làm. Nhưng với dev environment cần kernel-level access, systemd, hay mô phỏng multi-server topology — container không đủ. Thêm vào đó, trên Windows/macOS, Docker Desktop chạy trong một VM ẩn bên dưới; volume mount performance với dự án nhiều file nhỏ (như Laravel hay WordPress) là nỗi đau khó tả.

Cách 3: Vagrant — định nghĩa VM bằng code

Vagrant của HashiCorp (cùng nhà với Terraform, Vault) cho phép định nghĩa VM bằng một file text gọi là Vagrantfile. Toàn bộ cấu hình — OS, RAM, CPU, network, phần mềm cài sẵn — đều nằm trong file này và commit vào git cùng code dự án.

Tại sao Vagrantfile work cho bài toán này?

Bốn điểm mạnh chính, mỗi điểm đánh trúng một vấn đề đã nêu:

  • Reproducible: Chạy vagrant up trên bất kỳ máy nào, ra đúng một môi trường
  • Version controlled: File text thuần túy, commit được vào git, history thay đổi rõ ràng
  • Shareable: Developer mới clone repo, chạy vagrant up, xong — không cần đọc README 20 trang
  • Disposable: vagrant destroy khi xong việc, vagrant up lại từ đầu bất cứ lúc nào — môi trường sạch 100%

Cài đặt Vagrant từ đầu

Vagrant cần một hypervisor — mặc định là VirtualBox (miễn phí). Cài lần lượt như sau:

Bước 1: Cài VirtualBox

# Ubuntu/Debian
sudo apt install virtualbox

# macOS (dùng Homebrew)
brew install --cask virtualbox

Bước 2: Cài Vagrant

# Ubuntu/Debian — thêm repo chính thức của HashiCorp
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
brew install --cask vagrant

# Kiểm tra cài đặt thành công
vagrant --version

Vagrantfile thực tế cho dự án web stack LEMP

Dưới đây là Vagrantfile mình hay dùng cho stack LEMP (Linux + Nginx + MySQL + PHP):

Vagrant.configure("2") do |config|
  # Base box: Ubuntu 22.04 LTS
  config.vm.box = "ubuntu/jammy64"
  config.vm.hostname = "dev-lemp"

  # Port forwarding: truy cập web qua localhost:8080
  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "forwarded_port", guest: 3306, host: 33060

  # Private network để SSH và kết nối nội bộ
  config.vm.network "private_network", ip: "192.168.56.10"

  # Sync thư mục code từ máy host vào VM
  config.vm.synced_folder "./src", "/var/www/html"

  # Cấu hình tài nguyên VM
  config.vm.provider "virtualbox" do |vb|
    vb.name   = "dev-lemp"
    vb.memory = "2048"
    vb.cpus   = 2
  end

  # Provisioning: tự động cài phần mềm khi vagrant up lần đầu
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update -y
    apt-get install -y nginx mysql-server php8.1-fpm php8.1-mysql
    systemctl enable nginx
    systemctl start nginx
    echo "Môi trường dev LEMP sẵn sàng!"
  SHELL
end

Với file này, developer mới clone repo về và chạy đúng hai lệnh là xong:

# Lần đầu: tạo VM và cài đặt tất cả (mất khoảng 5-10 phút)
vagrant up

# SSH vào VM
vagrant ssh

# Tắt VM khi không dùng (giữ nguyên state)
vagrant halt

# Bật lại VM (nhanh hơn, không provision lại)
vagrant up

# Reload và re-provision khi sửa Vagrantfile
vagrant reload --provision

# Xóa hoàn toàn VM
vagrant destroy

Kết nối IDE với VM qua SSH

VSCode Remote SSH và JetBrains Gateway đều hỗ trợ kết nối trực tiếp vào Vagrant VM. Lấy SSH config bằng:

# In ra thông tin SSH config để dùng với IDE
vagrant ssh-config

# Hoặc thêm vào ~/.ssh/config luôn
vagrant ssh-config >> ~/.ssh/config

Sau đó trong VSCode chỉ cần Remote-SSH → Connect to Host → chọn dev-lemp. Code trên máy host, chạy trực tiếp trong VM — latency gần như zero vì cùng một máy vật lý.

Vagrant hay Docker — dùng cái nào?

Câu hỏi này hay gặp lắm. Sau nhiều năm dùng cả hai, mình đúc kết ngắn gọn:

  • Dùng Vagrant khi cần mô phỏng server thật — full OS, systemd, hoặc topology nhiều máy (ví dụ: 1 web server + 1 DB server + 1 load balancer)
  • Dùng Docker khi cần dev environment nhanh, nhẹ, và app đã được container hóa sẵn
  • Dùng cả hai: Vagrant tạo VM, bên trong VM chạy Docker. Setup mình đang dùng trong homelab: Proxmox tạo VM → VM chạy Vagrant → Vagrant provision Docker — ba tầng nhưng mỗi tầng có vai trò rõ ràng

Team đang bị hành bởi vấn đề môi trường không đồng nhất? Thêm một Vagrantfile vào repo là bước nhanh nhất. Mất 30 phút viết file, tiết kiệm hàng chục giờ debug về sau — và quan trọng hơn, không còn màn tranh luận “lỗi này do máy ai” trước mỗi sprint review nữa.

Share: