- Python 100%
| .gitignore | chore: ignore env files | |
| create_vm.py | chore: fix linting | |
| LICENSE | docs: License - MIT for simplicity - just don't sue me | |
| README.md | docs: License - MIT for simplicity - just don't sue me | |
RHEL VM Creation Script
Automated RHEL VM creation tool using KVM/QEMU with cloud-init configuration. This script streamlines the process of creating virtual machines from RHEL template images with automatic user configuration, SSH key setup, and customizable resources.
Features
- Automatic image discovery - Finds the latest RHEL image from
_RAW/<Date>/directories - Cloud-init integration - Automated user creation, SSH key setup, and system configuration
- User convenience - Uses your current username and SSH public key automatically
- Secure password handling - Interactive password prompt (no hardcoded defaults); supports SSH key-only mode
- Resource customization - Configure memory, vCPUs, and network settings
- Multiple RHEL versions - Supports RHEL 8, 9, and 10
- Dry-run mode - Preview what will be created without making changes
- Optional package installation - Control whether packages are installed during boot
Requirements
Hardware
- A CPU with hardware virtualisation support (Intel VT-x or AMD-V)
Verify it is enabled:
grep -Ec '(vmx|svm)' /proc/cpuinfo
# Output > 0 means virtualisation is available
Host OS
The script is designed for Fedora / RHEL / CentOS Stream hosts. The commands below use dnf.
Software dependencies
| Tool | Package | Required | Purpose |
|---|---|---|---|
python3 |
python3 |
Yes | Runs the script (uses stdlib only, no pip dependencies) |
openssl |
openssl |
Yes | Hashes the VM user password with SHA-512 crypt |
virt-install |
virt-install |
Yes | Creates and registers the VM with libvirt |
virsh |
libvirt-client |
Yes | VM management (start, stop, console, IP lookup) |
qemu-kvm |
qemu-kvm |
Yes | Kernel-based virtualisation backend |
libvirtd |
libvirt |
Yes | Virtualisation daemon |
qemu-img |
qemu-img |
Optional | Resizes the disk image when --disk-size is used |
virt-manager |
virt-manager |
Optional | GUI for managing VMs |
Installation (Fedora / RHEL)
# Core requirements
sudo dnf install -y python3 openssl qemu-kvm libvirt libvirt-client virt-install
# Optional: disk resize support
sudo dnf install -y qemu-img
# Optional: graphical VM manager
sudo dnf install -y virt-manager
# Enable and start the virtualisation daemon
sudo systemctl enable --now libvirtd
Verify the setup:
# Check libvirtd is running
sudo systemctl status libvirtd
# Check virt-install is available
virt-install --version
# Check openssl supports SHA-512 crypt (should print a 6ドル$ hash)
echo test | openssl passwd -6 -stdin
RHEL template images
Place downloaded RHEL KVM guest images in _RAW/<YYYY-MM-DD>/:
_RAW/
└── 2026年01月02日/
├── rhel-8.10-x86_64-kvm.qcow2
├── rhel-9.7-x86_64-kvm.qcow2
└── rhel-10.1-x86_64-kvm.qcow2
Images can be downloaded from Red Hat Customer Portal (requires a Red Hat account or active subscription).
SSH key
An SSH public key in ~/.ssh/ is strongly recommended (ed25519, RSA, or ECDSA). Without one, a console password is required to access the VM. Generate a key if needed:
ssh-keygen -t ed25519 -C "your_email@example.com"
Quick Start
Create a basic RHEL 10 VM:
./create_vm.py --rhel 10 --hostname rhel10-lab01
This will:
- Find the latest RHEL 10 image
- Create a
rhel10-lab01/directory - Prompt you to choose SSH key-only login or set a console password
- Generate cloud-init configuration files
- Copy the disk image
- Create and start the VM
Command-Line Options
Required Arguments
| Option | Short | Description |
|---|---|---|
--rhel |
-r |
RHEL version: 8, 9, or 10 |
--hostname |
-n |
Hostname for the VM (can be FQDN or short name) |
Image Selection (Optional)
| Option | Default | Description |
|---|---|---|
--minor |
latest | Minor version to use (e.g., --minor 0 for 10.0) |
--image |
auto | Path to specific qcow2 image file (overrides --rhel and --minor) |
Optional Arguments
| Option | Short | Default | Description |
|---|---|---|---|
--memory |
-m |
4096 | Memory in MB |
--vcpus |
-c |
2 | Number of vCPUs |
--disk-size |
-d |
template size | Disk size in GB |
--network |
default |
Network to connect to | |
--timezone |
system timezone | Timezone for the VM | |
--ca-url |
none | URL to download custom CA certificate | |
--ssh-key |
auto-detect | Path to SSH public key file to add to the VM | |
--install-packages |
false | Install packages during cloud-init (requires repo access) | |
--dry-run |
false | Show what would be done without creating the VM |
Usage Examples
Basic VM Creation
./create_vm.py --rhel 10 --hostname rhel10-lab01
VM with FQDN
./create_vm.py --rhel 9 --hostname rhel9-web01.example.com
Custom Resources
./create_vm.py --rhel 10 --hostname rhel10-db01 --memory 8192 --vcpus 4
Specific Minor Version
# Use RHEL 10.0 instead of the latest 10.x
./create_vm.py --rhel 10 --minor 0 --hostname rhel10-lab01
# Use RHEL 9.4 specifically
./create_vm.py --rhel 9 --minor 4 --hostname rhel9-web01
Specific Image File
# Use a specific qcow2 image directly
./create_vm.py --rhel 10 --image /path/to/custom-rhel10.qcow2 --hostname rhel10-custom
Custom Disk Size
# 50 GB disk
./create_vm.py --rhel 10 --hostname rhel10-lab01 --disk-size 50
# 100 GB disk with more resources
./create_vm.py --rhel 10 --hostname rhel10-db01 --memory 8192 --vcpus 4 --disk-size 100
Note: If --disk-size is not specified, the VM uses the original template image size.
Custom Network
./create_vm.py --rhel 8 --hostname rhel8-test --network bridge=br0
Custom Timezone
./create_vm.py --rhel 10 --hostname rhel10-lab01 --timezone America/New_York
./create_vm.py --rhel 9 --hostname rhel9-web01 --timezone Asia/Tokyo
./create_vm.py --rhel 10 --hostname rhel10-db01 --timezone UTC
Custom CA Certificate
# Install a custom root CA certificate on first boot
./create_vm.py --rhel 10 --hostname rhel10-lab01 --ca-url https://pki.example.com/root-ca.crt
The certificate will be downloaded to /etc/pki/ca-trust/source/anchors/ and update-ca-trust will be run automatically during cloud-init.
Custom SSH Key
# Use a specific SSH key instead of the auto-detected one
./create_vm.py --rhel 10 --hostname rhel10-lab01 --ssh-key ~/.ssh/id_ed25519.pub
# Useful when your default key differs from what you want on the VM
./create_vm.py --rhel 9 --hostname rhel9-web01 --ssh-key /path/to/team-key.pub
Note: When --ssh-key is specified, auto-detection of keys from ~/.ssh/ is skipped entirely.
With Package Installation
./create_vm.py --rhel 10 --hostname rhel10-lab01 --install-packages
Note: Package installation requires repository access. Without --install-packages, packages can be installed manually after boot to avoid cloud-init failures.
Dry Run Example
Test without creating the VM:
./create_vm.py --rhel 10 --hostname rhel10-lab01 --memory 8192 --vcpus 4 --dry-run
Output:
============================================================
RHEL VM Creation Script
============================================================
RHEL Version: 10
VM Name: rhel10-lab01
Hostname: rhel10-lab01
Memory: 8192 MB
vCPUs: 4
Network: default
============================================================
Step 1: Locating source disk image...
Found: _RAW/2026-01-02/rhel-10.1-x86_64-kvm.qcow2
Step 2: Creating VM directory...
Created: /home/user/VirtualMachines/rhel10-lab01
Step 3: Creating cloud-init configuration files...
Created: rhel10-lab01/user-data.yaml
Created: rhel10-lab01/meta-data.yaml
Step 4: Copying disk image...
Copying disk image...
From: _RAW/2026-01-02/rhel-10.1-x86_64-kvm.qcow2
To: rhel10-lab01/rhel-10.1-x86_64-kvm.qcow2
Disk image copied successfully
DRY RUN MODE - VM creation skipped
To create the VM manually, run:
sudo virt-install --name rhel10-lab01 --memory 8192 --vcpus 4 \
--disk path=rhel10-lab01/rhel-10.1-x86_64-kvm.qcow2 --import --os-variant rhel10.0 \
--network network=default --graphics spice \
--cloud-init user-data=rhel10-lab01/user-data.yaml,meta-data=rhel10-lab01/meta-data.yaml
What Gets Created
For a VM named rhel10-lab01, the script creates:
rhel10-lab01/
├── rhel-10.1-x86_64-kvm.qcow2 # Disk image (copy from _RAW)
├── user-data.yaml # Cloud-init user configuration
└── meta-data.yaml # Cloud-init metadata
Authentication modes
When the script runs, it prompts you to choose how the VM user can log in:
Option 1 — SSH key only (recommended)
An SSH key is configured. Choose login method:
1) SSH key only (recommended — no console password set)
2) SSH key + console password
Choice [1/2, default 1]:
- Password login is disabled; the account is locked
ssh_pwauthis set toFalsein cloud-init- Console (
virsh console) requires an alternative method to recover access (e.g. re-running the script or editing the image offline)
Option 2 — SSH key + console password
- You are prompted to enter and confirm a password (minimum 8 characters, no newlines)
- The password is hashed with SHA-512 crypt (
openssl passwd -6) before being written touser-data.yaml— the plaintext password is never stored anywhere - Both SSH key and password authentication are enabled
If no SSH key is found, a password is always required.
user-data.yaml Configuration
The generated cloud-init configuration includes:
- User account: Your current username (from
$USER) - Password: Entered interactively and stored as a SHA-512 hash; or account locked (SSH key-only mode)
- SSH key: Key specified via
--ssh-key, or auto-detected from~/.ssh/ - Sudo access: Passwordless sudo enabled
- Timezone: System timezone (customizable with
--timezone) - SSH access: Key-based always; password authentication only if a password was set
- Root account: Disabled for security
- Packages: Optional (disabled by default to avoid cloud-init failures)
Accessing Your VM
SSH Access (Primary Method)
# Get VM IP address
sudo virsh domifaddr rhel10-lab01
# Connect via SSH (uses your SSH key automatically)
ssh user@<vm-ip>
Console Access (Fallback)
# Connect to VM console
sudo virsh console rhel10-lab01
Login with the username printed at the end of the script and the password you entered during setup. If you chose SSH key-only mode, console password login is disabled — use SSH instead.
To exit the console, press Ctrl+]
VM Management
List VMs
# List all VMs
sudo virsh list --all
# List running VMs only
sudo virsh list
Control VMs
# Start a VM
sudo virsh start rhel10-lab01
# Stop a VM gracefully
sudo virsh shutdown rhel10-lab01
# Force stop a VM
sudo virsh destroy rhel10-lab01
# Delete a VM (keeps disk files)
sudo virsh undefine rhel10-lab01
# Delete a VM and its disk
sudo virsh undefine rhel10-lab01 --remove-all-storage
VM Information
# Get VM info
sudo virsh dominfo rhel10-lab01
# Get VM IP address
sudo virsh domifaddr rhel10-lab01
# View VM console output
sudo virsh console rhel10-lab01
Directory Structure
VirtualMachines/
├── _RAW/ # Template images (gitignored)
│ └── 2026年01月02日/
│ ├── rhel-8.10-x86_64-kvm.qcow2
│ ├── rhel-9.7-x86_64-kvm.qcow2
│ └── rhel-10.1-x86_64-kvm.qcow2
├── rhel10-lab01/ # VM directory (gitignored)
│ ├── rhel-10.1-x86_64-kvm.qcow2
│ ├── user-data.yaml
│ └── meta-data.yaml
├── create_vm.py # Main script
├── .gitignore # Excludes sensitive files
└── README.md # This file
Troubleshooting
Cloud-init Failures
Problem: cloud-final.service fails during boot
Cause: Package installation fails due to missing repository access
Solution: Don't use --install-packages flag. Install packages manually after boot:
ssh user@<vm-ip>
sudo dnf install vim wget curl git
SSH Key Not Found
Problem: Warning about missing SSH public key
Solution: Either generate an SSH key pair, or specify a key path explicitly with --ssh-key:
# Generate ed25519 key (recommended)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Or generate RSA key
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# Or point to an existing key directly
./create_vm.py --rhel 10 --hostname rhel10-lab01 --ssh-key /path/to/key.pub
VM Already Exists
Problem: Directory already exists
Solution: The script will prompt you to overwrite. You can also manually remove the directory:
rm -rf rhel10-lab01/
Can't Find VM After Creation
Problem: VM not showing in virsh list
Solution: Check if it's stopped:
sudo virsh list --all
sudo virsh start rhel10-lab01
Network Issues
Problem: Can't get VM IP address
Solution: Check network and restart VM:
# Check network
sudo virsh net-list --all
sudo virsh net-start default
# Restart VM
sudo virsh destroy rhel10-lab01
sudo virsh start rhel10-lab01
Security Considerations
- Passwords: No hardcoded defaults — you set the password interactively, or choose SSH key-only mode. Passwords are hashed with SHA-512 crypt before being written to cloud-init files; the plaintext is never stored
- SSH Keys: Only your SSH key is added to the VM
- Root Access: Root login is disabled by default
- Firewall: Configure firewall rules as needed for your use case
- Updates: Remember to apply security updates regularly
Version Control
The .gitignore file is configured to exclude:
- Cloud-init files (contain passwords and SSH keys)
- Disk images (large binary files)
- VM directories
- RAW image directories
Only the script and documentation are version controlled.
Manual Package Installation
If you didn't use --install-packages, install common packages after boot:
# Connect to VM
ssh user@<vm-ip>
# Install common utilities
sudo dnf install -y vim wget curl git
# Install development tools
sudo dnf groupinstall -y "Development Tools"
# Install specific packages as needed
sudo dnf install -y htop tmux tree
Advanced Usage
Using Different Networks
# Bridge network
./create_vm.py --rhel 10 --hostname rhel10-lab01 --network bridge=br0
# Custom network
./create_vm.py --rhel 10 --hostname rhel10-lab01 --network network=custom-net
Large VMs for Database Workloads
./create_vm.py --rhel 10 --hostname rhel10-db01 \
--memory 16384 \
--vcpus 8 \
--disk-size 200
Development VMs
./create_vm.py --rhel 9 --hostname rhel9-dev01 \
--memory 8192 \
--vcpus 4
Help
View all available options:
./create_vm.py --help
License
This project is licensed under the MIT License.
Lab icons created by Nhor Phai - Flaticon
Contributing
To add features or fix issues:
- Test changes with
--dry-runfirst - Update this README if adding new options
- Ensure
.gitignoreexcludes sensitive files