PostgreSQL DBA Lab: Build the VM with Vagrant
Table of Contents
Prerequisites
Install these on your Windows laptop:
- Oracle VirtualBox
- Vagrant
- PuTTY (to log in to the VM)
- WinSCP (optional, to copy the LVM script into the VM)
Open a Command Prompt and confirm the tools work:
C:\> vagrant --version
C:\> "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" --version
Note: Free disk space on the laptop matters. The disks are dynamic, so they start small and grow as data is written.
Lab Setup
| Item | Value |
|---|---|
| VM name | pg-dba-01 |
| Hostname | pgdba01.lab.local |
| IP address (host-only) | 192.168.2.201 |
| Operating system | Oracle Linux 9 (Vagrant box oraclelinux/9) |
| Memory / CPU | 3072 MB / 1 vCPU |
| Login | root / oracle |
| Vagrant folder | C:\VM\PGDBA |
| VM files folder | C:\VM\PGDBA |
| Disk files folder | C:\VM\PGDBA\pg-dba-01 |
Disk and mount point layout:
| Mount point | Disk size | Volume group | Logical volume |
|---|---|---|---|
/pgdata | 100 GB | vg_pgsql_pgdata | lv_pgsql_pgdata |
/tsdata | 100 GB | vg_pgsql_tsdata | lv_pgsql_tsdata |
/pgbackup | 200 GB (pgdata + tsdata) | vg_pgsql_pgbackup | lv_pgsql_pgbackup |
/pgarch | 100 GB | vg_pgsql_pgarch | lv_pgsql_pgarch |
/pglogs | 50 GB | vg_pgsql_pglogs | lv_pgsql_pglogs |
Sizing rule: /pgbackup is the size of /pgdata plus /tsdata, so it can hold one full copy of both.
Step 1: Build the VM with Vagrant
Create the folder C:\VM\PGDBA and save the following as a file named Vagrantfile (no extension). Only the five highlighted lines at the top need editing.
# ===== Edit these 5 lines =====
VM_NAME = "pg-dba-01"
VM_HOSTNAME = "pgdba01.lab.local"
VM_IP = "192.168.2.201"
VM_MEMORY = 3072
VM_CPUS = 1
# ==============================
Vagrant.configure("2") do |config|
config.vm.box = "oraclelinux/9"
config.vm.box_url = "https://oracle.github.io/vagrant-projects/boxes/oraclelinux/9.json"
config.vm.box_check_update = false
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.network "private_network", ip: VM_IP, auto_config: false
config.vm.hostname = VM_HOSTNAME
config.vm.provider "virtualbox" do |vb|
vb.name = VM_NAME
vb.memory = VM_MEMORY
vb.cpus = VM_CPUS
end
config.vm.provision "shell",
env: { "VM_IP" => VM_IP, "VM_HOSTNAME" => VM_HOSTNAME },
inline: <<~'SHELL'
set -e
echo "== 1. RHCK kernel as default =="
dnf install -y kernel lvm2 xfsprogs
RHCK=$(ls /boot/vmlinuz-* | grep -v uek | grep -v rescue | sort -V | tail -1)
grubby --set-default="$RHCK"
echo "== 2. Hostname and host-only IP =="
hostnamectl set-hostname "$VM_HOSTNAME"
NAT_IF=$(ip route show default | awk '{print $5}' | head -1)
HO_IF=$(ip -o link show | awk -F': ' '{print $2}' | grep -v -E "^(lo|${NAT_IF})$" | head -1)
nmcli -g NAME con show | grep -x hostonly >/dev/null 2>&1 && nmcli con delete hostonly
nmcli con add type ethernet ifname "$HO_IF" con-name hostonly \
ipv4.method manual ipv4.addresses ${VM_IP}/24 ipv6.method ignore \
connection.autoconnect yes
nmcli con up hostonly
grep -q "$VM_IP" /etc/hosts || echo "$VM_IP $VM_HOSTNAME ${VM_HOSTNAME%%.*}" >> /etc/hosts
echo "== 3. Root login and password (Putty: 192.168.2.201 : root/oracle)=="
echo "root:oracle" | chpasswd
cat > /etc/ssh/sshd_config.d/00-lab.conf <<'EOF'
PermitRootLogin yes
PasswordAuthentication yes
EOF
sshd -t
systemctl restart sshd
SHELL
end
What the provisioning script does:
- Installs the RHCK kernel,
lvm2andxfsprogs, and makes RHCK the default kernel. - Sets the hostname and gives the second (host-only) network adapter your static IP.
- Sets the root password to
oracleand allows root login over SSH, so PuTTY works.
C:\> cd "C:\Program Files\Oracle\VirtualBox"
C:\Program Files\Oracle\VirtualBox> VBoxManage setproperty machinefolder "C:\VM\PGDBA"
Note: machinefolder changes the default VM folder of VirtualBox for your whole laptop, not only for this lab. Vagrant has no setting for the VM location, so this is the simplest way.
Build the VM:
C:\> cd C:\VM\PGDBA
C:\VM\PGDBA> vagrant up
The first run downloads the Oracle Linux 9 box, so it takes a few minutes. When it finishes, the VM is running with hostname and IP already configured.
Step 2: Log in and Shut Down
Before connecting, check the host-only adapter on Windows. PuTTY can only reach the VM if your laptop has an IP address in the same network (192.168.2.x). Open:
WIN+R -- ncpa.cpl
— OR —
Control Panel\All Control Panel Items\Network and Sharing Center
--> Change adapter settings
--> right-click VirtualBox Host-Only Ethernet Adapter --> Properties
--> select Internet Protocol Version 4 (TCP/IPv4) --> Properties

Select Use the following IP address and enter:
| Field | Value |
|---|---|
| IP address | 192.168.2.1 |
| Subnet mask | 255.255.255.0 |
| Default gateway / DNS | Leave empty |
Click OK, then check from a Command Prompt that the VM answers:
C:\> ipconfig
C:\> ping 192.168.2.201
Tip: Shortcut to the same screen: press Win + R, type ncpa.cpl and press Enter. If the adapter already shows 192.168.2.1, no change is needed. Use any free address in the VM’s network, but never the VM’s own IP (192.168.2.201).
Open PuTTY and connect with these values:
| PuTTY field | Value |
|---|---|
| Host Name | 192.168.2.201 |
| Port | 22 |
| Login / password | root / oracle |
Check the hostname and the IP address, then shut the VM down so we can add disks:
[root@pgdba01 ~]# hostname
pgdba01.lab.local
[root@pgdba01 ~]# ip -4 addr show | grep 192.168.2
inet 192.168.2.201/24 brd 192.168.2.255 scope global noprefixroute eth1
[root@pgdba01 ~]# shutdown -h now
Step 3: Add 5 Dynamic Disks
Disks can only be attached while the VM is powered off.
C:\> cd "C:\Program Files\Oracle\VirtualBox"
C:\Program Files\Oracle\VirtualBox> VBoxManage list runningvms
Check the name of the storage controller. The default for this box is SATA Controller:
C:\Program Files\Oracle\VirtualBox> VBoxManage showvminfo pg-dba-01 | findstr /i "controller"
3.1 Create the disks. The size is in MB. --variant Standard means the disk is dynamically allocated.
VBoxManage createmedium disk --filename "C:\VM\PGDBA\pg-dba-01\pgdata.vdi" --size 102400 --format VDI --variant Standard
VBoxManage createmedium disk --filename "C:\VM\PGDBA\pg-dba-01\tsdata.vdi" --size 102400 --format VDI --variant Standard
VBoxManage createmedium disk --filename "C:\VM\PGDBA\pg-dba-01\pgbackup.vdi" --size 204800 --format VDI --variant Standard
VBoxManage createmedium disk --filename "C:\VM\PGDBA\pg-dba-01\pgarch.vdi" --size 102400 --format VDI --variant Standard
VBoxManage createmedium disk --filename "C:\VM\PGDBA\pg-dba-01\pglogs.vdi" --size 51200 --format VDI --variant Standard
| Disk file | Size in GB | Size in MB |
|---|---|---|
pgdata.vdi | 100 | 102400 |
tsdata.vdi | 100 | 102400 |
pgbackup.vdi | 200 | 204800 |
pgarch.vdi | 100 | 102400 |
pglogs.vdi | 50 | 51200 |
3.2 Attach the disks. Ports 1 to 5 are used because port 0 is the OS disk.
VBoxManage storageattach pg-dba-01 --storagectl "SATA Controller" --port 1 --device 0 --type hdd --medium "C:\VM\PGDBA\pg-dba-01\pgdata.vdi"
VBoxManage storageattach pg-dba-01 --storagectl "SATA Controller" --port 2 --device 0 --type hdd --medium "C:\VM\PGDBA\pg-dba-01\tsdata.vdi"
VBoxManage storageattach pg-dba-01 --storagectl "SATA Controller" --port 3 --device 0 --type hdd --medium "C:\VM\PGDBA\pg-dba-01\pgbackup.vdi"
VBoxManage storageattach pg-dba-01 --storagectl "SATA Controller" --port 4 --device 0 --type hdd --medium "C:\VM\PGDBA\pg-dba-01\pgarch.vdi"
VBoxManage storageattach pg-dba-01 --storagectl "SATA Controller" --port 5 --device 0 --type hdd --medium "C:\VM\PGDBA\pg-dba-01\pglogs.vdi"
Confirm that all five disks are attached:
C:\Program Files\Oracle\VirtualBox> VBoxManage showvminfo pg-dba-01 | findstr /i ".vdi"
Note: Keep the order exactly as shown. Linux names the disks sdb to sdf in port order, and the LVM script in Step 5 uses them in the same order. Replace pg-dba-01 with your VM name and C:\VM\PGDBA\pg-dba-01 with your own folder.
Step 4: Start the VM and Check the Disks
C:\> cd "C:\Program Files\Oracle\VirtualBox"
C:\Program Files\Oracle\VirtualBox> VBoxManage startvm "pg-dba-01"
Log in with PuTTY again and list the disks:
[root@pgdba01 ~]# lsblk -d -o NAME,SIZE,TYPE
NAME SIZE TYPE
sda 37G disk
sdb 100G disk
sdc 100G disk
sdd 200G disk
sde 100G disk
sdf 50G disk
Step 5: Create VG, LV and Mount Points
Create the script inside the VM with vi /root/lvm-setup.sh and paste the content below.
#!/usr/bin/env bash
# Run inside the VM as root: bash lvm-setup.sh
# One disk -> one VG -> one LV -> one mount point.
set -euo pipefail
[ "$EUID" -eq 0 ] || { echo "Run as root"; exit 1; }
# ===== Edit here =====
FS_TYPE="xfs"
# name : expected disk size in GB : mount point (disks are used in this order: sdb, sdc, sdd ...)
# VG = vg_pgsql_<name>, LV = lv_pgsql_<name>
VOLS=(
"pgdata:100:/pgdata"
"tsdata:100:/tsdata"
"pgbackup:200:/pgbackup"
"pgarch:100:/pgarch"
"pglogs:50:/pglogs"
)
# =====================
# Collect empty disks (no partitions, no filesystem, not LVM yet)
FREE_DISKS=()
for d in $(lsblk -dnpo NAME,TYPE | awk '$2=="disk"{print $1}' | sort); do
if [ "$(lsblk -n "$d" | wc -l)" -eq 1 ] && ! blkid "$d" >/dev/null 2>&1 && ! pvs "$d" >/dev/null 2>&1; then
FREE_DISKS+=("$d")
fi
done
echo "Empty disks found: ${FREE_DISKS[*]:-none}"
[ "${#FREE_DISKS[@]}" -ge "${#VOLS[@]}" ] || { echo "Need ${#VOLS[@]} empty disks, found ${#FREE_DISKS[@]}"; exit 1; }
i=0
for v in "${VOLS[@]}"; do
IFS=: read -r name gb mp <<< "$v"
disk="${FREE_DISKS[$i]}"; i=$((i+1))
vg="vg_pgsql_$name"; lv="lv_pgsql_$name"; dev="/dev/$vg/$lv"
actual=$(( $(lsblk -bdn -o SIZE "$disk") / 1073741824 ))
if [ "$actual" -ne "$gb" ]; then
echo "ERROR: $disk is ${actual}G but $name expects ${gb}G. Check disk order (lsblk) or edit VOLS."; exit 1
fi
echo "== $name: $disk (${gb}G) -> $vg/$lv -> $mp"
pvcreate -y "$disk"
vgcreate "$vg" "$disk"
lvcreate -y -l 100%FREE -n "$lv" "$vg"
mkfs."$FS_TYPE" "$dev"
mkdir -p "$mp"
grep -qs "^$dev " /etc/fstab || echo "$dev $mp $FS_TYPE defaults,nofail 0 0" >> /etc/fstab
done
systemctl daemon-reload
mount -a
if id postgres >/dev/null 2>&1; then
for v in "${VOLS[@]}"; do IFS=: read -r _ _ mp <<< "$v"; chown postgres:postgres "$mp"; done
fi
echo; vgs; lvs; df -hT | grep -E "pg|tsdata"
Run it:
[root@pgdba01 ~]# bash /root/lvm-setup.sh
What the script does for each disk:
pvcreateturns the disk into an LVM physical volume.vgcreatecreates the volume groupvg_pgsql_<name>on it.lvcreate -l 100%FREEcreates the logical volumelv_pgsql_<name>using the whole disk.mkfs.xfsformats it, then the script creates the mount point and adds a line to/etc/fstabso it mounts after every reboot.
Step 6: Verify the Result
[root@pgdba01 ~]# vgs
[root@pgdba01 ~]# lvs
[root@pgdba01 ~]# df -hT | grep -E "pg|tsdata"
You should see five volume groups, five logical volumes and five mounted XFS file systems, similar to this (sizes are slightly below the disk size because of LVM and file system overhead):
/dev/mapper/vg_pgsql_pgdata-lv_pgsql_pgdata xfs 100G 746M 100G 1% /pgdata
/dev/mapper/vg_pgsql_tsdata-lv_pgsql_tsdata xfs 100G 746M 100G 1% /tsdata
/dev/mapper/vg_pgsql_pgbackup-lv_pgsql_pgbackup xfs 200G 1.5G 199G 1% /pgbackup
/dev/mapper/vg_pgsql_pgarch-lv_pgsql_pgarch xfs 100G 746M 100G 1% /pgarch
/dev/mapper/vg_pgsql_pglogs-lv_pgsql_pglogs xfs 50G 389M 50G 1% /pglogs
Test that the mounts survive a reboot:
[root@pgdba01 ~]# reboot
# log in again
[root@pgdba01 ~]# df -hT | grep -E "pg|tsdata"
Cleanup
Remove the lab when you are finished:
C:\VM\PGDBA> vagrant destroy -f
Then delete the five .vdi files in C:\VM\PGDBA\pg-dba-01.
Warning: vagrant destroy deletes the VM and everything stored on it. The .vdi files stay on your disk until you delete them yourself.