Перед инженером часто стоит задача: дать команде из десяти человек одинаковое окружение, которое поднимается одной командой и в котором гарантированно нет фразы "а у меня локально работало". Vagrant решает именно это - он описывает виртуальную машину (или их связку) текстовым файлом, который лежит рядом с кодом в git. В этом уроке разберём структуру Vagrantfile, откуда берутся boxes, чем отличаются провайдеры libvirt и VirtualBox, как настраивается provisioning через shell и ansible, как работают синхронизация папок и проброс портов, и как собрать многомашинный стенд из нескольких узлов.

Как это работает
Vagrant - это не гипервизор и не контейнерный движок. Это оркестратор-обёртка на Ruby, который переводит декларативное описание в вызовы конкретного провайдера. Сам он ничего не виртуализирует: за создание ВМ отвечает libvirt/KVM, VirtualBox, Hyper-V или даже docker. Vagrant лишь говорит провайдеру "создай машину с такими ресурсами, из такого образа, прокинь такие папки и порты".
Базовый образ называется box. Это упакованный шаблон диска плюс метаданные: для какого провайдера он собран и какая версия. Box скачивается один раз в кэш (~/.vagrant.d/boxes) и используется как основа для всех ВМ. При vagrant up Vagrant делает linked clone от box - новая машина не копирует весь диск, а наследует его слоем поверх, поэтому поднимается за секунды.
Состояние конкретного проекта Vagrant хранит в скрытой папке .vagrant рядом с Vagrantfile. Там лежит привязка имени машины к её ID в провайдере. Поэтому Vagrantfile коммитят в репозиторий, а .vagrant добавляют в .gitignore.
Provisioning - это шаги, которые выполняются внутри гостя один раз при первом up (или принудительно по vagrant provision). Через них ставят пакеты, конфигурируют сервисы, накатывают ansible-плейбуки. Идея в том, что box остаётся чистым и универсальным, а вся специфика проекта описана в provisioning и тоже лежит в git.
Команды и примеры
Установка. В Debian 13 и Ubuntu 24.04 пакет vagrant есть в репозитории, но он обычно отстаёт - актуальнее ставить из репозитория HashiCorp. В RHEL 10 и Fedora 41+ аналогично через dnf.
Код: Выделить всё
# Debian/Ubuntu
sudo apt update
sudo apt install -y vagrant
# или свежее из репозитория HashiCorp (apt-репозиторий releases.hashicorp.com)
# RHEL/Fedora
sudo dnf install -y vagrant
Код: Выделить всё
# libvirt-стек (Debian/Ubuntu)
sudo apt install -y qemu-system-x86 libvirt-daemon-system virtinst
vagrant plugin install vagrant-libvirt
# libvirt-стек (RHEL/Fedora)
sudo dnf install -y @virtualization
sudo systemctl enable --now libvirtd
vagrant plugin install vagrant-libvirt
Код: Выделить всё
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian13"
config.vm.hostname = "web1"
config.vm.provider :libvirt do |lv|
lv.memory = 2048
lv.cpus = 2
end
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.synced_folder "./app", "/var/www/app", type: "nfs"
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y nginx
SHELL
end
Код: Выделить всё
vagrant up # создать и запустить, прогнать provisioning
vagrant ssh # зайти в гостя
vagrant status # состояние машин проекта
vagrant provision # перепрогнать provisioning без пересоздания
vagrant reload --provision # перезагрузить ВМ и заново провижинить
vagrant halt # выключить (диск сохраняется)
vagrant destroy -f # удалить ВМ целиком
vagrant box list # boxes в локальном кэше
Код: Выделить всё
vagrant up --provider=libvirt
vagrant up --provider=virtualbox
Код: Выделить всё
config.vm.provision "ansible" do |a|
a.playbook = "site.yml"
a.compatibility_mode = "2.0"
end
Многомашинное окружение. Внутри одного Vagrantfile описываем несколько define-блоков. Это типовой стенд "веб плюс база".
Код: Выделить всё
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian13"
config.vm.define "db" do |db|
db.vm.hostname = "db"
db.vm.network "private_network", ip: "192.168.56.20"
db.vm.provision "shell", inline: "apt-get update && apt-get install -y postgresql"
end
config.vm.define "web" do |web|
web.vm.hostname = "web"
web.vm.network "private_network", ip: "192.168.56.10"
web.vm.network "forwarded_port", guest: 80, host: 8080
web.vm.provision "ansible" do |a|
a.playbook = "web.yml"
end
end
end
Где брать boxes. Публичный каталог - Vagrant Cloud (app.vagrantup.com). Имена вида generic/debian13, bento/ubuntu-24.04, rockylinux/10. Семейство generic собрано сразу под несколько провайдеров, поэтому удобно для libvirt. Свой box делают из настроенной ВМ командой vagrant package или сборкой через Packer.
Частые грабли
- Box собран под другой провайдер. Образ под VirtualBox не запустится в libvirt и наоборот. Перед выбором смотрите, какие провайдеры поддерживает box на Vagrant Cloud; берите generic/* для кросс-провайдерности.
- synced_folder с типом nfs требует, чтобы на хосте работал NFS-сервер, а в Debian/Ubuntu - пакет nfs-kernel-server. Без него up падает на этапе монтирования. Тип virtualbox (vboxsf) и 9p/virtiofs (libvirt) такого не требуют, но медленнее или капризнее по правам.
- Дефолтная синхронизация . в /vagrant включена всегда. Если её тип несовместим с провайдером, машина не поднимется - отключайте строкой config.vm.synced_folder ".", "/vagrant", disabled: true.
- Конфликт портов на хосте: два проекта пробрасывают на host: 8080. Второй up выдаст ошибку занятого порта. Указывайте auto_correct: true либо разносите порты.
- vagrant up не перепрогоняет provisioning на уже созданной машине - он идёт только при первом создании. Для повторного нужен vagrant provision или up --provision.
- Плагин vagrant-libvirt чувствителен к версии Vagrant. После обновления пакета плагин может перестать грузиться - переустановите его через vagrant plugin install.
- Права на сокет libvirt: пользователь должен быть в группе libvirt, иначе Vagrant не достучится до демона и попросит root.
- Установите Vagrant и стек libvirt (KVM), добавьте себя в группу libvirt и перелогиньтесь.
- Создайте папку проекта, выполните vagrant init generic/debian13 и откройте сгенерированный Vagrantfile.
- Пропишите провайдер libvirt с 1 CPU и 1024 МБ памяти, проброс порта 80 на хост 8080 и shell-provisioning, ставящий nginx.
- Поднимите стенд: vagrant up --provider=libvirt. Проверьте nginx через curl http://localhost:8080 с хоста.
- Расширьте Vagrantfile до двух машин web и db с private_network и статическими IP; поднимите обе и убедитесь, что web пингует db по адресу.
- Сломайте provisioning намеренно (опечатка в имени пакета), запустите vagrant provision и разберите вывод ошибки; исправьте и перепрогоните.
- Уничтожьте стенд через vagrant destroy -f и убедитесь по vagrant box list, что сам box остался в кэше.
- Чем box отличается от запущенной Vagrant-машины и где физически хранится каждый из них?
- Почему один и тот же box нельзя использовать с любым провайдером и как это проверить заранее?
- В каких случаях provisioning выполняется автоматически, а в каких его нужно запускать вручную?
- Какие типы synced_folder доступны для libvirt и VirtualBox и какие у каждого требования к хосту?
- Как в многомашинном Vagrantfile адресовать отдельную ВМ для ssh и provisioning?
- Чем ansible отличается от ansible_local как способов provisioning и когда выбирают второй?