Create Multi-VM LAMP Stack on Oracle Virtualization
Introduction
At the core of the Oracle Virtualization solution is the Oracle Linux KVM host. On top of this hypervisor you can run many virtual machines - each running one of the supported operating systems including Oracle Linux, RHEL, Windows, Ubuntu, Solaris and others. This virtualization solution is managed through the Oracle Linux Virtualization Manager which offers a single interface to keep things organized. In addition to multi-host/multi-VM management, Oracle Linux Virtualization Manager enables key features of the Oracle Virtualization solution. For example, Oracle Linux Virtualization Manager provides automatic high availability with VM failover, live migration of VMs between hosts with zero downtime, role-based access control and audit logging, and simplified backup and disaster recovery workflows. These enterprise features transform Oracle Linux KVM from individual hosts into a true enterprise virtualization platform.
In this tutorial, we'll explore the core hypervisor technology that powers Oracle Virtualization. Kernel-based Virtual Machine (KVM) is an open-source type-1 (bare-metal) hypervisor. This functionality permits a host system, such as Oracle Linux, to host multiple virtual machines (VMs) or guests when running on supported hardware. You will deploy Oracle Linux KVM to create virtual machines configured with LAMP stack applications, enabling you to host complete web development environments with Linux, Apache, MySQL, and PHP components across multiple VMs.
Important: The two application codes in this lab are for educational purposes only. They are designed to help developers learn and practice application development skills with the LAMP stack. These codes are not intended for production environments and should not be used as-is in a live setting.
Objectives
- Setup OCI, Luna, or On-premise server
- Deploy Oracle Linux KVM
- Create Virtual Machine using Oracle Cloud Images
- Install MySQL server
- Install Web server with Apache and PHP
Prerequisites for non Luna Access
Any Oracle Linux system with the following configurations:
- a non-root user with sudo permissions
Step 1: Deploy Oracle Linux
Note: If not using Luna and running in your own tenancy, read the linux-virt-labs GitHub project README.md and complete the prerequisites before deploying the lab environment.
Quick Tip: Mac users use Command+C/Command+V for copy/paste, or two-finger click for right-click. Windows and Linux users, your usual Ctrl+C/Ctrl+V shortcuts work.
Open a terminal from the Luna Desktop. Click the Applications menu in the bottom-left corner and select Terminal Emulator.

Clone the
linux-virt-labsGitHub project. This contains all the Ansible automation that will build the KVM environment. You'll see about 1,500 files downloading - watch for the 'done' messages.git clone https://github.com/oracle-devrel/linux-virt-labs.gitChange into the working directory. Notice your prompt changes to show you're now inside 'linux-virt-labs/ol'. This is where all the deployment scripts live.
cd linux-virt-labs/olInstall the required collections. This command installs Ansible collections - think of these as plugin packages that extend Ansible's capabilities.
ansible-galaxy collection install -r requirements.ymlDeploy the lab environment.
ansible-playbook create_instance.yml -e localhost_python_interpreter="/usr/bin/python3.6" -e instance_ocpus="4" -e instance_memory="64"The free lab environment requires the extra variable
local_python_interpreter, which setsansible_python_interpreterfor plays running on localhost. This variable is needed because the environment installs the RPM package for the Oracle Cloud Infrastructure SDK for Python, located under the python3.6 modules.The default deployment shape uses the AMD CPU and Oracle Linux 8. To use an Intel CPU add
-e instance_shape="VM.Standard3.Flex"
⚠️ CRITICAL - Terminal Management Instructions
Wait for the playbook to run successfully and reach the pause task. You'll see multiple tasks executing as it creates your infrastructure. Each completed task shows "changed" or "ok" in the output.
When the playbook pauses, it will display your instance connection details:
- name: ol-node-01
- public ip: [Your unique IP address]
- private ip: [Internal OCI IP]
IMPORTANT: Do NOT press Enter in this terminal yet!
Required Actions:
- Write down your Public IP address - You'll need this throughout the lab
- LEAVE this terminal window open and untouched - Do not press Enter or close it
- Open a NEW terminal window for Step 2 and all remaining lab work
- Return to this terminal ONLY after completing Step 12 to clean up resources
Why? This paused playbook is keeping your OCI infrastructure alive. If you press Enter now, it will immediately delete all resources. You'll press Enter at the very end of the lab (after Step 12) to properly clean up.
Next Step: Open a second terminal window and proceed to Step 2.
Step 2: Validate Environment Supports Virtualization
Reminder: Use the NEW terminal window (Terminal #2) you just opened. Terminal #1 from Step 1 should remain paused and untouched.
In this new terminal, connect via SSH to the ol-node-01 instance using the Public IP you recorded in Step 1.
ssh opc@<YOUR ol-node-01 PUBLIC_IP>Example:
ssh
opc@132.145.198....https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)When prompted about the host fingerprint, type
yesand press Enter.Run the following command to determine your CPU type.
grep -e 'vendor_id' /proc/cpuinfo | uniqThe
vendor_idreports eitherAuthenticAMDfor an AMD CPU orGenuinIntelfor an Intel CPU.Check if the hardware supports virtualization.
Run the command that matches your CPU type.
For AMD CPUs - Verify the AMD-V CPU extensions exist:
grep -w -o 'svm' /proc/cpuinfo | uniqFor Intel CPUs - Verify the Intel VT CPU extensions exist:
grep -w -o 'vmx' /proc/cpuinfo | uniqThe existence of one of these flags (
svmorvmx) in the command output indicates this system supports virtualization. You can also use thelscpucommand and look for theVirtualizationentry in the output.Check for the loaded KVM modules.
lsmod | grep kvmThe output shows the KVM kernel modules. On OCI, these modules may already be loaded when the instance is created in Step 1, so they can appear before package installation or starting libvirtd.
Step 3: Install and Start KVM
Check the running version of Oracle Linux.
hostnamectl | grep 'Operating System'You should see Oracle Linux Server 8.10 or similar.
Install the associated software packages for Oracle Linux virtualization.
This command installs the virtualization packages, which include libvirt and other dependencies on Oracle Linux. The libvirt package provides a software library and API to manage KVM virtual machines and includes the libvirtd daemon that runs in the background to handle the actual VM management.
sudo dnf module install -y virtThis install will take several minutes. Watch for "Complete!" at the end.
Install
virt-install- tool for creating and configuring virtual machines (VMs) using KVM (Kernel-based Virtual Machine) hypervisor.sudo dnf install -y virt-installYou'll use this tool later to build your virtual machines.
Validate the host machine is ready and set up to run libvirt VMs.
virt-host-validateIf all checks pass, the system is prepared for creating VMs. You should see mostly "PASS" in green. If any checks fail, follow the instructions to correct the problem. If any check returns "WARN", consider following the instructions to improve the virtualization capabilities, but warnings won't prevent the lab from working.
Start the systemd (Linux service manager) service and enable it to start automatically on each boot.
sudo systemctl enable --now libvirtd.serviceThe
--nowflag starts the service immediately, andenablemakes it start automatically on every boot.Check the services status to confirm they are up and running.
sudo systemctl status libvirtd.serviceThe output shows the service as enabled and running. Look for "active (running)" in green. Press
qto exit the status view.
Step 4: Create two Virtual Machines using Oracle Cloud Images
Change to the KVM image storage location.
cd /var/lib/libvirt/imagesDownload the Oracle Linux VM template.
sudo curl -O https://yum.oracle.com/templates/OracleLinux/OL8/u10/x86_64/OL8U10_x86_64-kvm-b237.qcow2This downloads a pre-configured Oracle Linux 8.10 cloud image optimized for KVM.
Create VM-01 (Database)
Create a meta-data file for cloud-init configuration.
cat << 'EOF' | sudo tee ~/meta-data > /dev/null instance-id: iid-local01 local-hostname: vm-01 EOFCloud-init uses this metadata to configure the VM on first boot, setting the hostname and instance ID.
Create a user-data file.
cat << 'EOF' | sudo tee ~/user-data > /dev/null #cloud-config system_info: default_user: name: opc ssh_authorized_keys: - <paste_public_key_here> EOFGenerate an SSH key pair for secure connection to the VM
ssh-keygen -t rsa -b 4096When prompted, press
Enterthree times to accept all defaults (no passphrase needed for this lab). The command writes the key pair to the.sshdirectory in the user's home.Extract the generated public SSH key
SSHKEY=$(cat ~/.ssh/id_rsa.pub)This stores your public key in a variable for the next step. No output means success.
Update the user-data file with the generated public SSH key
sed -i "s|<paste_public_key_here>|${SSHKEY}|g" ~/user-dataThis automatically inserts your public SSH key into the user-data file, replacing the placeholder.
Generate an ISO image containing the user-data and meta-data files
sudo genisoimage -output /var/lib/libvirt/images/vm-01.iso -volid cidata -joliet -rock ~/user-data ~/meta-dataThis packages the configuration files into an ISO that the VM will mount on first boot to configure itself.
Find the OS variant that matches the downloaded image
osinfo-query os | grep ol8You should see
ol8.10in the output - we need this identifier for the VM creation command.Copy the Oracle Linux image to a new disk image for vm-01
sudo cp /var/lib/libvirt/images/OL8U10_x86_64-kvm-b237.qcow2 /var/lib/libvirt/images/vm-01.qcowThis creates an independent disk for VM-01 so our two VMs don't share the same disk file.
Create a new virtual machine named vm-01 with specified resources and configuration
sudo virt-install --name vm-01 \ --memory 2048 \ --vcpus 2 \ --disk /var/lib/libvirt/images/vm-01.qcow,device=disk,bus=virtio \ --disk /var/lib/libvirt/images/vm-01.iso,device=cdrom \ --os-type linux --os-variant ol8.10 \ --virt-type kvm --graphics none \ --network network=default,model=virtio \ --noautoconsole \ --importThis creates VM-01 with 2 CPUs and 2 GB RAM, attaches both the disk and cloud-init ISO, then boots the VM. You'll see "Domain is still running" confirming successful creation.
List all running virtual machines
sudo virsh listYou should see vm-01 listed with State "running". If it does not show, wait a minute and reenter the command.
Retrieve the IP address of the
vm-01virtual machine.sudo virsh net-dhcp-leases --network defaultLook for vm-01's IP address - usually something like 192.168.122.xxx. Write this down!
Verify VM-01 works by connecting with SSH.
ssh opc@<ip_address_of_vm-01>Example:
ssh
opc@192.168.122....https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)When prompted about the fingerprint, type
yesand press Enter. You're now inside your first virtual machine!Get details about the virtual machine.
hostnamectlVerify the hostname is
vm-01, OS is Oracle Linux 8.10, and under "Virtualization" it sayskvm.Exit the vm-01 server to continue to the next step
exitYour prompt changes back - you're now on ol-node-01 again, not inside the VM.
Create VM-02 (Web Server)
Create a meta-data file for vm-02
cat << 'EOF' | sudo tee ~/meta-data > /dev/null instance-id: iid-local02 local-hostname: vm-02 EOFSame process as VM-01, but with a different hostname and instance ID.
Generate an ISO image for vm-02
sudo genisoimage -output /var/lib/libvirt/images/vm-02.iso -volid cidata -joliet -rock ~/user-data ~/meta-dataWe're reusing the same user-data file with our SSH key, just changing the metadata.
Copy the Oracle Linux image to a new disk image for vm-02
sudo cp /var/lib/libvirt/images/OL8U10_x86_64-kvm-b237.qcow2 /var/lib/libvirt/images/vm-02.qcowEach VM gets its own independent disk image.
Create a new virtual machine named vm-02 with specified resources and configuration
sudo virt-install --name vm-02 \ --memory 2048 \ --vcpus 2 \ --disk /var/lib/libvirt/images/vm-02.qcow,device=disk,bus=virtio \ --disk /var/lib/libvirt/images/vm-02.iso,device=cdrom \ --os-type linux --os-variant ol8.10 \ --virt-type kvm --graphics none \ --network network=default,model=virtio \ --noautoconsole \ --importCreating VM-02 - our web server. Same specs as VM-01: 2 CPUs, 2 GB RAM.
List all running virtual machines
sudo virsh listNow you should see BOTH VMs running! VM-01 and VM-02 both listed with State "running". If VM-02 does not show, wait a minute and reenter the command.
Retrieve the IP address of the
vm-02virtual machine.sudo virsh net-dhcp-leases --network defaultFind VM-02's IP address and write it down next to VM-01's IP.
Verify the virtual machine works by connecting with
ssh.ssh opc@<ip_address_of_vm-02>Example:
ssh
opc@192.168.122....https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)Type
yesfor the fingerprint. You're now inside your second virtual machine!Get details about the virtual machine by running
hostnamectl.hostnamectlVerify hostname is
vm-02, same OS version, same virtualization type.Exit the vm-02 server to continue to the next step
exitBack on ol-node-01 again.
Extract and save VM-01's IP address to a variable.
VM01_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-01 | tail -1 | awk '{print $5}' | cut -d'/' -f1)No output means it worked - the IP is stored in the variable.
Extract and save VM-02's IP address to a variable.
VM02_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-02 | tail -1 | awk '{print $5}' | cut -d'/' -f1)Both IPs are now saved for easy reuse throughout the rest of the lab.
Verify VM-01 IP variable is set correctly.
echo "VM-01 (Web): $VM01_IP"You should see VM-01's IP printed out.
Verify VM-02 IP variable is set correctly.
echo "VM-02 (Web): $VM02_IP"You should see VM-02's IP printed out. Make sure both IPs display correctly!
Test SSH connection to VM-01 using the variable.
ssh -o ConnectTimeout=10 opc@$VM01_IP "echo 'VM-01 OK'"You should see "VM-01 OK" printed, confirming the variable works and SSH is accessible.
Test SSH connection to VM-02 using the variable.
ssh -o ConnectTimeout=10 opc@$VM02_IP "echo 'VM-02 OK'"You should see "VM-02 OK" printed. Both VMs are ready for the LAMP stack installation!
Congratulations! You now have two fully functional virtual machines running on your Oracle Linux KVM host. VM-01 will become your database server, and VM-02 will become your web server.
Step 5: Setup MySQL on VM-01
Install MySQL
From ol-node-01, establish an SSH connection to VM-01.
ssh opc@$VM01_IPNotice your prompt changes to show
vm-01- you're now inside the database VM.Download MySQL repository configuration
sudo yum -y install https://dev.mysql.com/get/mysql84-community-release-el8-1.noarch.rpmThis installs the MySQL 8.4 repository configuration.
Disable default MySQL module to avoid conflicts
sudo yum -y module disable mysqlInstall MySQL server and client
sudo yum install -y mysql mysql-serverThis is a large package with many dependencies. Watch for "Complete!" at the end.
Start MySQL service
sudo systemctl start mysqldOn first start, MySQL initializes the data directory and generates a temporary root password.
Enable MySQL service to start at boot
sudo systemctl enable mysqldThis ensures MySQL starts automatically when VM-01 reboots.
Allow incoming MySQL traffic through the firewall
sudo firewall-cmd --permanent --add-service=mysqlThis opens port 3306 so VM-02 can connect to this database. The
--permanentflag makes this persist across reboots.Reload firewall configuration to apply changes
sudo firewall-cmd --reloadMySQL is now accessible from other VMs on the network.
Configure MySQL
Extract temporary root password from MySQL log
TEMP_PASS=$(sudo grep 'temporary password' /var/log/mysqld.log | awk '{print $NF}')MySQL generates a random temporary password on first start. This command finds it in the log and saves it to a variable. No output means success.
Login to MySQL with temporary root password
mysql -uroot -p$TEMP_PASS --connect-expired-passwordYou'll see the MySQL prompt
mysql>appear. You're now inside the MySQL database!Change root password to a secure value
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Welcome#123';In production you'd use a more secure password, but for this lab we're using
Welcome#123. Press Enter - you'll see "Query OK" confirming the change.Create 'admin' user with a secure password
CREATE USER 'admin'@'%' IDENTIFIED BY 'Welcome#123';The
%means this user can connect from any host, not just localhost.Grant all privileges to 'admin' user
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION;This gives the admin user full administrative privileges.
Create 'empuser' user with a secure password
CREATE USER 'empuser'@'%' IDENTIFIED BY 'Welcome#123';This is the account our web application will use to access the employee database.
Grant all privileges to 'empuser'
GRANT ALL PRIVILEGES ON *.* TO 'empuser'@'%' WITH GRANT OPTION;In production you'd restrict this to specific databases, but for our lab we're keeping it simple.
Reload privilege tables to apply changes
FLUSH PRIVILEGES;This reloads the privilege tables, making all our changes take effect immediately. You'll see "Query OK".
Quit MySQL shell
\qYour prompt changes back from
mysql>to the VM-01 shell prompt. MySQL is now fully configured!Exit SSH session from VM-01
exitYour prompt changes back to ol-node-01. MySQL is installed, running, and ready to accept connections from VM-02.
MySQL Setup Complete! VM-01 is now configured as a database server with MySQL 8.4, firewall rules configured, and user accounts ready for the web application.
Step 6: Setup Apache/PHP on VM-02
From ol-node-01, establish an SSH connection to VM-02.
ssh opc@$VM02_IPNotice your prompt changes to show
vm-02- you're now inside the web server VM.Install Apache HTTP server
sudo yum install -y httpdThis installs the Apache web server - the "A" in our LAMP stack. Watch for "Complete!" at the end.
Install PHP 8.2 and its dependencies
sudo dnf install -y @php:8.2This is the "P" in LAMP. The
@php:8.2syntax installs the entire PHP module group.Install PHP MySQL and JSON extensions
sudo yum install -y php-mysqlnd php-jsonThese extensions let our PHP code communicate with the MySQL database on VM-01 and handle JSON data.
Enable and start Apache HTTP server
sudo systemctl enable --now httpdThe
--nowflag starts Apache immediately, andenablemakes it start automatically on boot. Apache is now running and serving web pages!Allow incoming HTTP traffic on port 80
sudo firewall-cmd --permanent --add-port=80/tcpThis opens port 80 for HTTP traffic so web browsers can reach our Apache server.
Reload firewall configuration to apply changes
sudo firewall-cmd --reloadHTTP port 80 is now accessible.
Allow Apache to connect to network resources
sudo setsebool -P httpd_can_network_connect 1This configures SELinux to allow Apache to make outbound network connections. Without this, Apache couldn't connect to the MySQL database on VM-01. The
-Pmakes this permanent across reboots.Exit SSH session from VM-02
exitYour prompt changes back to ol-node-01. VM-02 now has a complete LAMP stack - Linux, Apache, MySQL client libraries, and PHP. We're ready to test!
Apache/PHP Setup Complete! VM-02 is now configured as a web server with Apache, PHP 8.2, firewall rules, and SELinux permissions ready for the LAMP stack applications.
Step 7: Test Apache
Display the IP address of VM-02.
echo "VM-02 (Web): $VM02_IP"Copy this IP address - you'll need it for the SSH tunnel command.
Open a NEW terminal window from the Luna Desktop.
- Click the Applications menu (bottom-left corner)
- Select Terminal Emulator
- A new terminal window will open
Important: This creates a third terminal window:
- Terminal #1 - Paused Ansible playbook from Step 1 ⚠️ Still open, don't touch
- Terminal #2 - Your main working terminal (where you just started step 2)
- Terminal #3 - SSH tunnel (new terminal you just opened)
In this NEW terminal (Terminal #3), create an SSH tunnel to access VM-02's web server.
ssh -L 8081:<YOUR VM_02 IP>:80 opc@<YOUR ol-node-01 PUBLIC_IP>Example:
ssh -L 8081:192.168.122...:80
opc@132.145.198....https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)Replace both placeholders:
<YOUR VM_02 IP>- The IP you just displayed in step 4 (like 192.168.122.xxx)<YOUR ol-node-01 PUBLIC_IP>- The public IP from Step 1 (like 132.145.198.xxx)
What this does: The tunnel forwards traffic from your Luna Desktop port 8081 to VM-02's port 80. When you browse to localhost:8081, traffic flows through the tunnel to VM-02's web server.
⚠️ Critical: This terminal must stay open while you browse! Don't close it or the tunnel will disconnect.
Open Firefox inside Luna Desktop and test Apache.
- Click Applications menu
- Select Web Browser (Firefox)
- Navigate to:
http://localhost:8081
You should see the Oracle Linux Apache test page (purple/white page). This confirms Apache is running and the tunnel works!
Important: Leave the SSH tunnel (Terminal #3) and Firefox open for later use throughout Steps 8-12.
Apache Test Complete! Your web server is accessible through the SSH tunnel. You now have three terminal windows open and Firefox ready for testing web applications.
Step 8: Create Test Application
Return to your main working terminal (Terminal #2) on ol-node-01.
Make sure you're working in Terminal #2, not the tunnel terminal (Terminal #3).
Retrieve and save VM-01's IP address.
VM01_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-01 | tail -1 | awk '{print $5}' | cut -d'/' -f1)This refreshes the VM-01 IP variable in case it was lost. No output means success.
Retrieve and save VM-02's IP address.
VM02_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-02 | tail -1 | awk '{print $5}' | cut -d'/' -f1)Both VM IPs are now saved and ready to use.
From ol-node-01, establish an SSH connection to VM-02.
ssh opc@$VM02_IPYour prompt changes to
vm-02- you're now on the web server.Create a PHP info page to display PHP configuration
sudo tee /var/www/html/info.php > /dev/null << 'EOF' <?php phpinfo(); ?> EOFThis creates a simple page that displays all PHP configuration details - useful for verifying PHP is working correctly.
Create database connection test application
sudo tee /var/www/html/dbtest.php > /dev/null << 'EOF' <?php echo "<h1>Multi-VM LAMP Stack Test</h1>"; // Database connection details define('DB_SERVER', '$VM01_IP'); define('DB_USERNAME', 'admin'); define('DB_PASSWORD', 'Welcome#123'); define('DB_NAME', 'mysql'); echo "<p>Testing connection to MySQL at: " . DB_SERVER . "</p>"; // Test network connectivity $fp = @fsockopen(DB_SERVER, 3306, $errno, $errstr, 5); if (!$fp) { echo "<p style='color: red;'>ERROR: Cannot reach MySQL server</p>"; echo "<p>Error: $errstr ($errno)</p>"; } else { echo "<p style='color: green;'>✓ Network connection successful</p>"; fclose($fp); // Test MySQL connection $link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME); if($link === false){ echo "<p style='color: red;'>ERROR: Could not connect to MySQL</p>"; echo "<p>Error: " . mysqli_connect_error() . "</p>"; } else { echo "<p style='color: green;'>✓ Successfully Connected to MySQL!</p>"; echo "<p>MySQL Version: " . mysqli_get_server_info($link) . "</p>"; echo "<p>Host Info: " . mysqli_get_host_info($link) . "</p>"; mysqli_close($link); } } ?> EOFThis PHP script tests both network connectivity and MySQL authentication between VM-02 and VM-01. Notice the placeholder
$VM01_IP- we'll replace that next.Set proper file permissions for Apache.
sudo chown apache:apache /var/www/html/*.phpApache needs to own these files to read and execute them.
Edit dbtest.php to replace the placeholder with VM-01's actual IP address.
sudo vi /var/www/html/dbtest.phpIn the vi editor:
- Type
/DB_SERVERand press Enter (searches for the line) - Press
ito enter insert mode - Navigate to
$VM01_IPand replace it with your actual VM-01 IP (like192.168.122.100) - Press
Esc - Type
:wqand press Enter (saves and quits)
Before: define('DB_SERVER', '$VM01_IP');
After: define('DB_SERVER', '192.168.122.xxx');
- Type
Exit VM-02
exitYour prompt changes back to ol-node-01. The test applications are now created and ready to access!
Test Applications Created! You now have two PHP applications on VM-02:
info.php- Displays PHP configurationdbtest.php- Tests database connectivity to VM-01
Step 9: Access Your Webserver and Database Test application
Verify your SSH tunnel from Step 7 is still open.
Check Terminal #3 - it should still be connected and showing the ol-node-01 prompt. If you accidentally closed it, recreate the tunnel:
ssh -L 8081:$VM02_IP:80 opc@<YOUR ol-node-01 PUBLIC_IP>Test PHP configuration - In Firefox, navigate to:
http://localhost:8081/info.phpYou should see a detailed PHP configuration page with purple headers showing "PHP Version 8.2.x". This confirms PHP is installed and working correctly. Scroll down to see all loaded modules and settings.
Test database connectivity - In Firefox, navigate to:
http://localhost:8081/dbtest.phpExpected Results:
You should see:
- Heading: "Multi-VM LAMP Stack Test"
- Green checkmark: "✓ Network connection successful"
- Green checkmark: "✓ Successfully Connected to MySQL!"
- MySQL version information
- Host info showing the connection to VM-01
If you see all green messages, congratulations! Your two VMs are successfully communicating. The web tier on VM-02 is connecting to the database tier on VM-01. This proves your multi-VM architecture is working!
Database Connectivity Verified! Your LAMP stack is fully functional with VM-02 (web server) successfully connecting to VM-01 (database server) across the virtual network.
Step 10: Create and Load Employee Database
Verify you're in your main working terminal (Terminal #2) on ol-node-01.
Make sure you're not in the tunnel terminal (Terminal #3) or inside a VM.
Retrieve and save VM-01's IP address.
VM01_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-01 | tail -1 | awk '{print $5}' | cut -d'/' -f1)Refreshing the variable. No output means success.
Retrieve and save VM-02's IP address.
VM02_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-02 | tail -1 | awk '{print $5}' | cut -d'/' -f1)Both IPs are now saved.
From ol-node-01, establish an SSH connection to VM-01.
ssh opc@$VM01_IPYour prompt changes to
vm-01- you're now on the database server where we'll load the employee data.Change to the /tmp directory.
cd /tmpWe'll download the database files here temporarily.
Download MySQL employee sample database
sudo curl -L -o employees_db_full.zip "https://objectstorage.us-ashburn-1.oraclecloud.com/p/5UYZYk1vh241OqeHp_J0xnzBpzUOxZtTgaqCH16OP7HpOjC71W207gAY9EY1rW2U/n/idazzjlcjqzj/b/mysql-ee-downloads/o/employees_db_full.zip"This downloads Oracle's official MySQL test dataset with realistic employee data. You'll see a progress bar.
Install the unzip utility.
sudo dnf install -y unzipQuick install to extract the database archive.
Extract the database files.
sudo unzip employees_db_full.zipYou'll see several SQL files being extracted.
Change to the extracted database directory.
cd employees_db_fullThis directory contains all the SQL scripts to create and populate the employee database.
Load the employee database into MySQL.
mysql -u admin -pWelcome#123 < employee.sqlThis creates tables and inserts over 300,000 employee records. You'll see table creation messages scrolling by. This is the real data our web application will display!
Verify the database loaded successfully.
mysql -u admin -pWelcome#123 -e "USE employee; SHOW TABLES; SELECT COUNT(*) FROM employee;"Expected Output:
You should see a list of tables including:
employeedepartmentsalarytitle
And the employee count should show: 300,024 records
Exit database VM
exitYour prompt changes back to ol-node-01. VM-01 now has a production-ready employee database with 300,000+ records!
Employee Database Loaded! VM-01 now contains:
- 300,024 employee records
- 9 departments
- 400,000+ salary records
- Complete organizational data ready for the web application
Step 11: Create Employee Database Web Application
From ol-node-01, establish an SSH connection to VM-02.
ssh opc@$VM02_IPYour prompt changes to
vm-02- you're now on the web server where we'll create the employee application.Create a professional employee database application with modern styling.
sudo tee /var/www/html/employee.php > /dev/null << 'EOF' <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Employee Database - Multi-VM LAMP Demo</title> <style> body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-align: center; padding: 30px; border-radius: 15px; margin-bottom: 20px; box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .info-box { background: rgba(255,255,255,0.9); padding: 25px; border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); margin-bottom: 20px; backdrop-filter: blur(10px); } .success { color: #28a745; font-weight: bold; } .error { color: #dc3545; font-weight: bold; } table { width: 100%; border-collapse: collapse; margin: 20px 0; background: rgba(255,255,255,0.9); border-radius: 12px; overflow: hidden; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } th, td { padding: 15px; text-align: left; border-bottom: 1px solid #ddd; } th { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight: bold; } tr:hover { background-color: rgba(102, 126, 234, 0.1); } .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin: 20px 0; } .stat-card { background: white; padding: 20px; border-radius: 10px; text-align: center; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .stat-number { font-size: 2em; font-weight: bold; color: #667eea; } </style> </head> <body> <div class="header"> <h1>🏢 Employee Database Demo</h1> <p>MySQL Employee Sample Database on Multi-VM Architecture</p> </div> <?php // Database connection details define('DB_SERVER', '$VM01_IP'); define('DB_USERNAME', 'empuser'); define('DB_PASSWORD', 'Welcome#123'); define('DB_NAME', 'employee'); try { // Connect to MySQL database on separate VM $pdo = new PDO("mysql:host=" . DB_SERVER . ";dbname=" . DB_NAME, DB_USERNAME, DB_PASSWORD); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo '<div class="info-box success">'; echo '<h2>✅ Connected to Employee Database</h2>'; echo '<p><strong>Database Server:</strong> ' . DB_SERVER . ' (vm-01)</p>'; echo '<p><strong>Web Server:</strong> ' . gethostname() . ' (vm-02)</p>'; echo '</div>'; // Get database statistics $stats = []; $stmt = $pdo->query("SELECT COUNT(*) as count FROM employee"); $stats['employee'] = $stmt->fetch()['count']; $stmt = $pdo->query("SELECT COUNT(*) as count FROM department"); $stats['departments'] = $stmt->fetch()['count']; $stmt = $pdo->query("SELECT COUNT(*) as count FROM salary"); $stats['salaries'] = $stmt->fetch()['count']; $stmt = $pdo->query("SELECT COUNT(*) as count FROM title"); $stats['titles'] = $stmt->fetch()['count']; echo '<div class="info-box">'; echo '<h2>📊 Database Statistics</h2>'; echo '<div class="stats">'; echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['employee']) . '</div><div>Employee</div></div>'; echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['departments']) . '</div><div>Departments</div></div>'; echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['salaries']) . '</div><div>Salary Records</div></div>'; echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['titles']) . '</div><div>Job Titles</div></div>'; echo '</div>'; echo '</div>'; // Show recent employee echo '<div class="info-box">'; echo '<h2>👥 Sample Employee Data</h2>'; $stmt = $pdo->query("SELECT emp_no, first_name, last_name, gender, hire_date FROM employee ORDER BY hire_date DESC LIMIT 20"); $employee = $stmt->fetchAll(); echo '<table>'; echo '<thead><tr><th>Employee #</th><th>First Name</th><th>Last Name</th><th>Gender</th><th>Hire Date</th></tr></thead>'; echo '<tbody>'; foreach ($employee as $emp) { echo '<tr>'; echo '<td>' . htmlspecialchars($emp['emp_no']) . '</td>'; echo '<td>' . htmlspecialchars($emp['first_name']) . '</td>'; echo '<td>' . htmlspecialchars($emp['last_name']) . '</td>'; echo '<td>' . htmlspecialchars($emp['gender']) . '</td>'; echo '<td>' . htmlspecialchars($emp['hire_date']) . '</td>'; echo '</tr>'; } echo '</tbody></table>'; echo '</div>'; // Show departments echo '<div class="info-box">'; echo '<h2>🏬 Departments</h2>'; $stmt = $pdo->query("SELECT dept_no, dept_name FROM department ORDER BY dept_name"); $departments = $stmt->fetchAll(); echo '<table>'; echo '<thead><tr><th>Department Code</th><th>Department Name</th></tr></thead>'; echo '<tbody>'; foreach ($departments as $dept) { echo '<tr>'; echo '<td>' . htmlspecialchars($dept['dept_no']) . '</td>'; echo '<td>' . htmlspecialchars($dept['dept_name']) . '</td>'; echo '</tr>'; } echo '</tbody></table>'; echo '</div>'; } catch (PDOException $e) { echo '<div class="info-box error">'; echo '<h2>❌ Database Connection Error</h2>'; echo '<p>Error: ' . htmlspecialchars($e->getMessage()) . '</p>'; echo '</div>'; } ?> <div class="info-box"> <h2>🏗️ Multi-VM Architecture</h2> <p><strong>Database VM (vm-01):</strong> MySQL Server with Employee Database</p> <p><strong>Web VM (vm-02):</strong> Apache + PHP Web Application</p> <p><strong>Data Source:</strong> MySQL Sample Employee Database</p> <p><strong>Records:</strong> 300,000+ employee, 400,000+ salary records</p> </div> </body> </html> EOFThis creates a production-ready web application with:
- Modern gradient design and responsive layout
- Database connection verification
- Real-time statistics from 300,000+ employee records
- Sample employee data display (20 most recent hires)
- Complete department listing
Set proper file permissions for Apache.
sudo chown apache:apache /var/www/html/employee.phpApache needs to own this file to read and execute it.
Edit employee.php to replace the placeholder with VM-01's actual IP address.
sudo vi /var/www/html/employee.phpIn the vi editor:
- Type
/DB_SERVERand press Enter (searches for the line) - Press
ito enter insert mode - Navigate to
$VM01_IPand replace it with your actual VM-01 IP (like192.168.122.100) - Press
Esc - Type
:wqand press Enter (saves and quits)
Before: define('DB_SERVER', '$VM01_IP');
After: define('DB_SERVER', '192.168.122.xxx');
- Type
Exit VM-02 back to the host.
exitYour prompt changes back to ol-node-01. The professional employee application is now ready to display 300,000+ employee records!
Employee Application Created! A production-ready web application is now deployed on VM-02, ready to display comprehensive employee data from VM-01's MySQL database with modern styling and real-time statistics.
Step 12: Access Employee Application
Verify your SSH tunnel from Step 7 is still open.
Check Terminal #3 - it should still be connected and showing the ol-node-01 prompt. If you accidentally closed it, recreate the tunnel
ssh -L 8081:$VM02_IP:80 opc@<YOUR ol-node-01 PUBLIC_IP>Example:
ssh -L 8081:192.168.122...:80
opc@132.145.198....https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)In Firefox, navigate to the employee application:
http://localhost:8081/employee.phpWhat You'll See:
- Purple gradient header: "Employee Database Demo"
- Green success message: "Connected to Employee Database"
- Database Statistics cards: Showing 300,024 employees, 9 departments, 400,000+ salary records
- Sample Employee Data table: 20 most recently hired employees with names, employee numbers, and hire dates
- Departments table: All 9 company departments listed
- Architecture summary: Confirming the multi-VM setup
If you see all this beautiful data displayed with modern styling - congratulations! You've successfully built a complete multi-tier LAMP stack application running across two virtual machines!
Lab Complete! Congratulations!
You have successfully:
- Deployed Oracle Linux KVM infrastructure in the cloud
- Created and configured two virtual machines from scratch
- Built a complete multi-tier LAMP stack (Linux, Apache, MySQL, PHP)
- Separated database and web tiers for security and scalability
- Loaded a production-ready database with 300,000+ employee records
- Deployed a professional web application with modern UI
- Verified full connectivity across your multi-VM architecture
Your Architecture:
- VM-01: MySQL 8.4 database server with employee database
- VM-02: Apache + PHP 8.2 web application server
- Networking: Virtual bridge connecting both VMs
- Access: Secure SSH tunnel for web application access
What's Next? This KVM foundation you've built is exactly what Oracle Linux Virtualization Manager (OLVM) manages at enterprise scale. OLVM adds centralized management, high availability, live migration, and automated failover on top of the KVM infrastructure you mastered today.
Essential Management Commands
VM Management
List running VMs and their status
sudo virsh listStart VM-01
sudo virsh start vm-01Shutdown VM-01
sudo virsh shutdown vm-01Display DHCP leases for VMs on the default network
sudo virsh net-dhcp-leases --network default
Setup For running this lab on Oracle Cloud Infrastructure
Virtual Cloud Network Setup
Navigate: Networking → Virtual Cloud Networks → Start VCN Wizard - Name: ol-vcn-01 - Create VCN Navigate: Networking → Virtual Cloud Networks → kvm-network → Security Lists Add these ingress rules: - SSH: Source 0.0.0.0/0, TCP Port 22 - HTTP: Source 0.0.0.0/0, TCP Port 80Compute Instance Setup:
Navigation: Hamburger Menu → Compute → Instances → Create Instance Configuration: - Name: ol-node-01 - Image: Oracle Linux 8 (Latest) - Shape: VM.Standard.E4.Flex (4 OCPUs, 16GB RAM) - Boot Volume: 100 GB - VCN: ol-vcn-01 - Subnet: Public subnet ol-vcn-01 - Assign Public IP: Yes - Add SSH Key: Upload your public keyTest Compute public IP with SSH
ssh opc@<ol-vcn-01_PUBLIC_IP>Continue to Step 2: Validate Environment Supports Virtualization
Next Steps
Learn to manage hosts and vms with Oracle Linux Virtual Manager (OLVM)