Building an Azure Home Lab: A Student-Friendly Journey from Virtual Network to Private MySQL

Building an Azure Home Lab: A Student-Friendly Journey from Virtual Network to Private MySQL

Cloud platforms can feel overwhelming when you first start learning them. There are so many services, so many networking concepts, and so many commands that it is easy to lose sight of the bigger picture.

In this tutorial, we will walk through a simple but very useful Azure lab setup:

  • A Virtual Network (VNet) to hold our private infrastructure
  • A Windows Server 2022 Virtual Machine
  • An Azure Database for MySQL Flexible Server
  • A Private DNS zone so the VM can talk to the database privately by name
  • Optional public connectivity for RDP so we can log in to the VM for testing

This lab is great for students because it introduces several core Azure fundamentals in one journey:

  • Resource organization
  • Virtual networking
  • Subnets
  • Managed database services
  • DNS resolution
  • Private connectivity
  • Infrastructure creation with Azure CLI

The goal is not just to run commands, but to understand what each Azure service does and why each command matters.


What We Are Building

At a high level, the environment looks like this:

  1. We create a Resource Group to organize resources.
  2. We create a VNet with two subnets:
    • one subnet for the VM
    • one delegated subnet for MySQL Flexible Server
  3. We create a Private DNS Zone for Azure Database for MySQL private name resolution.
  4. We link that DNS zone to the VNet.
  5. We deploy a MySQL Flexible Server into the delegated subnet using private access.
  6. We create a database inside that MySQL server.
  7. We deploy a Windows Server 2022 VM into the VM subnet.
  8. Optionally, we attach a Public IP and NSG rule to allow RDP access for administration.
  9. The VM can then resolve the MySQL server’s private FQDN and communicate with it over the private network.

This is a good lab because it shows the difference between public access and private access in Azure.


Why This Lab Matters for Azure Fundamentals

Students often learn cloud services one by one, but real environments involve multiple services working together.

This lab teaches several important Azure concepts:

1. Resource Group

A Resource Group is a logical container for Azure resources. It helps you organize, manage, and delete related resources together.

2. Virtual Network

A VNet is your private network in Azure. It is similar to a network you would build on-premises, except it exists in the cloud.

3. Subnets

Subnets divide a VNet into smaller network segments. Different workloads can be separated into different subnets for control and security.

4. Delegation

Some Azure managed services, like MySQL Flexible Server with private access, need a subnet delegated to that service. Delegation tells Azure that the subnet is reserved for a specific resource type.

5. Private DNS

DNS translates names into IP addresses. With private services, internal DNS is important so your VM can resolve the database server’s private hostname correctly.

6. Platform as a Service (PaaS)

MySQL Flexible Server is a managed database service. Microsoft manages much of the database infrastructure, while you focus on configuration and usage.

7. Infrastructure as Code Mindset

Even though this example uses shell variables and CLI commands rather than Terraform or Bicep, it still encourages automation and repeatability.


The Variables Section

The script begins with variables:

RG=zz
LOC=australiaeast

VNET=vnet-app
VNET_CIDR=10.10.0.0/16
VM_SUBNET=sn-vm
VM_SUBNET_CIDR=10.10.1.0/24
MYSQL_SUBNET=sn-mysql
MYSQL_SUBNET_CIDR=10.10.2.0/24

PRIV_DNS_ZONE=privatelink.mysql.database.azure.com

VM=vm-ws2022
VM_ADMIN=azureadmin
VM_PASSWORD='Replace-With-Strong-P@ssw0rd!'

MYSQL=myfs-mysql-$RANDOM
MYSQL_ADMIN=mysqladmin
MYSQL_PASSWORD='Replace-With-Strong-P@ssw0rd!'
MYSQL_DB=appdb

This is a good habit because it makes the script easier to read and maintain.

What each variable does

  • RG is the name of the Resource Group.
  • LOC is the Azure region, here australiaeast.
  • VNET is the Virtual Network name.
  • VNET_CIDR defines the IP address range of the VNet.
  • VM_SUBNET and VM_SUBNET_CIDR define the subnet for the virtual machine.
  • MYSQL_SUBNET and MYSQL_SUBNET_CIDR define the subnet for the MySQL service.
  • PRIV_DNS_ZONE is the private DNS zone used for MySQL private endpoint-style name resolution in this scenario.
  • VM, VM_ADMIN, and VM_PASSWORD define the VM settings.
  • MYSQL, MYSQL_ADMIN, MYSQL_PASSWORD, and MYSQL_DB define the MySQL server and database settings.

A nice touch here is:

MYSQL=myfs-mysql-$RANDOM

This helps create a unique server name, which is important because some Azure resource names must be globally unique.


Step 1: Create the Resource Group

az group create -n $RG -l $LOC

What this command does

This creates a new Azure Resource Group.

  • az group create tells Azure CLI to create a resource group
  • -n $RG sets the name
  • -l $LOC sets the region

Why this matters

Resource Groups are one of the first Azure concepts students should learn. They help you:

  • group related resources together
  • apply permissions and policies more easily
  • delete a whole lab in one action when finished

For a student lab, this is very useful because cleanup becomes simple and less error-prone.


Step 2: Create the Virtual Network and VM Subnet

az network vnet create \
  -g $RG -n $VNET -l $LOC \
  --address-prefixes $VNET_CIDR \
  --subnet-name $VM_SUBNET --subnet-prefixes $VM_SUBNET_CIDR

What this command does

This creates:

  • the Virtual Network
  • the first subnet for the VM

Command breakdown

  • az network vnet create creates a VNet
  • -g $RG places it in the Resource Group
  • -n $VNET names the VNet
  • -l $LOC places it in the chosen region
  • --address-prefixes $VNET_CIDR sets the overall IP range of the VNet
  • --subnet-name $VM_SUBNET creates a subnet inside the VNet
  • --subnet-prefixes $VM_SUBNET_CIDR gives the subnet its IP range

Azure service explained: Virtual Network

An Azure VNet is the foundation of private networking in Azure. It lets your resources communicate securely with one another.

In this case:

  • The VNet address space is 10.10.0.0/16
  • The VM subnet is 10.10.1.0/24

That means the VM will live in its own network segment.

Why this matters

Good cloud design separates workloads into subnets. Even in a lab, that teaches the right architecture habits early.


Step 2b: Create the Delegated Subnet for MySQL

az network vnet subnet create \
  -g $RG --vnet-name $VNET -n $MYSQL_SUBNET \
  --address-prefixes $MYSQL_SUBNET_CIDR \
  --delegations Microsoft.DBforMySQL/flexibleServers

What this command does

This adds a second subnet to the VNet, specifically for MySQL Flexible Server.

Command breakdown

  • az network vnet subnet create creates a subnet
  • --vnet-name $VNET places the subnet in the existing VNet
  • -n $MYSQL_SUBNET names the subnet
  • --address-prefixes $MYSQL_SUBNET_CIDR assigns the subnet range
  • --delegations Microsoft.DBforMySQL/flexibleServers delegates the subnet to the MySQL Flexible Server service

Azure service explained: Subnet delegation

Delegation is an Azure networking feature that allows a subnet to be reserved for a specific Azure managed service.

Here, we are saying:

This subnet is intended for Azure Database for MySQL Flexible Server.

Why this matters

This is a very important cloud networking concept. Some PaaS services integrate into your virtual network, but they need Azure to know the subnet is intended for that service type.

Without this delegation, the MySQL private deployment would fail.


Step 3: Create the Private DNS Zone

az network private-dns zone create -g $RG -n $PRIV_DNS_ZONE

What this command does

This creates a Private DNS Zone.

Azure service explained: Azure Private DNS

DNS translates hostnames into IP addresses.

Public DNS is used for internet-facing resources.
Private DNS is used for internal name resolution inside private networks.

In this lab, the private DNS zone is:

privatelink.mysql.database.azure.com

This lets internal Azure resources resolve the private name of the MySQL server correctly.

Why this matters

Students often focus on compute and databases, but DNS is one of the hidden pieces that makes systems actually work.

Your VM does not want to connect to a database by memorizing an IP address. It wants a stable hostname. Private DNS makes that possible.


Step 3b: Get the VNet Resource ID

VNET_ID=$(az network vnet show -g $RG -n $VNET --query id -o tsv)

What this command does

This retrieves the Azure resource ID of the VNet and stores it in a shell variable.

Command breakdown

  • az network vnet show displays information about the VNet
  • --query id extracts only the id field
  • -o tsv outputs it as plain text for shell usage

Why this matters

Many Azure commands need a resource ID instead of just a name. This is common in automation.

Students should get comfortable with the pattern:

  • create something
  • retrieve its ID
  • pass that ID into another command

Step 3c: Link the Private DNS Zone to the VNet

az network private-dns link vnet create \
  -g $RG -n link-$VNET \
  --zone-name $PRIV_DNS_ZONE \
  --virtual-network $VNET_ID \
  --registration-enabled false

What this command does

This links the Private DNS Zone to the VNet.

Command breakdown

  • az network private-dns link vnet create creates the DNS-VNet link
  • --zone-name $PRIV_DNS_ZONE chooses the DNS zone
  • --virtual-network $VNET_ID specifies the VNet
  • --registration-enabled false disables automatic DNS registration

Why registration is false

Automatic registration is useful when you want VMs in a VNet to automatically register hostnames in a private zone.

Here, this zone is intended for the MySQL service namespace, so we do not need VM auto-registration.

Why this matters

Creating a DNS zone alone is not enough. The network must be linked to it so resources in that VNet can actually use it for name resolution.

This is one of those steps that beginners often miss.


Step 4: Get the MySQL Subnet ID

MYSQL_SUBNET_ID=$(az network vnet subnet show -g $RG --vnet-name $VNET -n $MYSQL_SUBNET --query id -o tsv)

What this command does

This fetches the resource ID of the MySQL subnet.

Just like we did with the VNet ID, we use:

  • show to inspect the subnet
  • --query id to grab the resource ID
  • -o tsv for clean shell output

Why this matters

The MySQL creation command needs the subnet reference so Azure knows exactly where to place the private service integration.


Step 4b: Create the MySQL Flexible Server

az mysql flexible-server create \
  -g $RG -n $MYSQL -l $LOC \
  --admin-user $MYSQL_ADMIN --admin-password $MYSQL_PASSWORD \
  --subnet $MYSQL_SUBNET_ID \
  --private-dns-zone $PRIV_DNS_ZONE \
  --sku-name Standard_B2ms \
  --storage-size 32 \
  --version 9.3

What this command does

This creates an Azure Database for MySQL Flexible Server using private network access.

Azure service explained: MySQL Flexible Server

Azure Database for MySQL Flexible Server is a managed MySQL database offering.

Microsoft handles many infrastructure tasks such as:

  • platform maintenance
  • patching of the service platform
  • backup capabilities
  • high availability options depending on configuration
  • managed deployment experience

You still manage things like:

  • database users
  • schema
  • application connectivity
  • performance tuning choices within the service limits

Command breakdown

  • az mysql flexible-server create creates the server
  • -g $RG -n $MYSQL -l $LOC sets the Resource Group, name, and region
  • --admin-user and --admin-password define the database administrator
  • --subnet $MYSQL_SUBNET_ID places the server in the delegated subnet
  • --private-dns-zone $PRIV_DNS_ZONE associates it with the private DNS zone
  • --sku-name Standard_B2ms selects the compute tier and size
  • --storage-size 32 sets allocated storage
  • --version 9.3 sets the MySQL engine version

Why private access is important

In this design, the database is not meant to be broadly exposed to the internet. Instead, it is reachable from inside the private network.

That is a strong lesson for students:
not every service should have a public endpoint.

About the SKU

Standard_B2ms is a smaller burstable SKU, which makes sense for a lab or test environment. It helps keep costs lower while still providing enough resources for learning and experimentation.


Step 4c: Create a Database Inside the Server

az mysql flexible-server db create \
  -g $RG -s $MYSQL -d $MYSQL_DB

What this command does

This creates a database named appdb inside the MySQL server.

Command breakdown

  • az mysql flexible-server db create creates a database
  • -g $RG specifies the Resource Group
  • -s $MYSQL specifies the MySQL server
  • -d $MYSQL_DB specifies the database name

Why this matters

The server is the database platform instance, but applications usually connect to a specific database within that server.

This distinction is useful for students:

  • server = the managed MySQL service instance
  • database = one logical database inside that server

Step 5: Get the VM Subnet ID

VM_SUBNET_ID=$(az network vnet subnet show -g $RG --vnet-name $VNET -n $VM_SUBNET --query id -o tsv)

What this command does

This retrieves the ID of the VM subnet so the VM can be created in the correct subnet.

Again, this reinforces a common CLI automation pattern: retrieve resource metadata and pass it into later commands.


Step 5b: Create the Windows Server 2022 VM

az vm create \
  -g $RG -n $VM -l $LOC \
  --image "MicrosoftWindowsServer:WindowsServer:2022-datacenter:latest" \
  --size Standard_D2s_v5 \
  --admin-username $VM_ADMIN \
  --admin-password "$VM_PASSWORD" \
  --subnet $VM_SUBNET_ID \
  --public-ip-address "" \
  --nsg "" \
  --assign-identity

What this command does

This creates a Windows Server 2022 Virtual Machine in the VM subnet.

Azure service explained: Azure Virtual Machines

Azure Virtual Machines are Infrastructure as a Service (IaaS). They give you a full operating system instance in the cloud.

With a VM, you are responsible for more than with a managed database service. For example, you manage:

  • operating system configuration
  • application installation
  • patching strategy
  • security hardening
  • remote administration

Command breakdown

  • az vm create creates the VM
  • --image selects the OS image
  • --size Standard_D2s_v5 chooses 2 vCPU and 8 GiB memory
  • --admin-username and --admin-password define local admin credentials
  • --subnet $VM_SUBNET_ID places the VM in the VM subnet
  • --public-ip-address "" means no public IP is created during deployment
  • --nsg "" means do not automatically create an NSG
  • --assign-identity enables a managed identity for the VM

Why create the VM without public IP first?

This is actually a smart design habit. By default, keeping the VM private reduces exposure.

You can later decide whether remote access should be provided through:

  • Bastion
  • VPN
  • Just-in-time access
  • temporary public IP and NSG rules

Azure service explained: Managed Identity

The --assign-identity flag creates a system-assigned managed identity for the VM.

This allows the VM to authenticate to supported Azure services without storing credentials in scripts or files.

For students, this is a great concept to learn early because it supports more secure cloud design.


Optional Step: Add Public IP and NSG for RDP

The script then adds optional RDP access.

Create a Public IP

az network public-ip create -g $RG -n pip-$VM --sku Standard --zone 1 2 3 --allocation-method static

This creates a Standard SKU static Public IP.

Why this matters

  • Public IP gives the VM internet-reachable addressing
  • Static means the IP remains fixed
  • Zone 1 2 3 improves resiliency options depending on regional support

Create a Network Security Group

az network nsg create -g $RG -n nsg-$VM

This creates an NSG, which is basically a packet filtering firewall for Azure network traffic.

Create an NSG Rule for RDP

az network nsg rule create -g $RG --nsg-name nsg-$VM -n Allow-RDP --priority 1000 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes '*' --source-port-ranges '*' --destination-port-ranges 3389

This allows inbound TCP traffic on port 3389, which is used for Remote Desktop Protocol.

Important security note

This is convenient for a home lab, but not a best practice for production.
Allowing RDP from * means from anywhere on the internet, which is risky.

For learning, it can be acceptable if tightly controlled and short-lived.
For production, this should be replaced with safer access methods.


Attach the Public IP to the VM NIC

First, the script gets the NIC ID:

NIC_ID=$(az vm show -g $RG -n $VM --query 'networkProfile.networkInterfaces[0].id' -o tsv)
NIC_NAME=$(basename $NIC_ID)

What this does

  • az vm show retrieves details about the VM
  • --query 'networkProfile.networkInterfaces[0].id' extracts the first NIC ID
  • basename strips the resource path and leaves just the NIC name

Then the script updates the IP configuration:

az network nic ip-config update -g $RG --nic-name $NIC_NAME -n ipconfig-ws2022 --public-ip-address pip-$VM

This attaches the Public IP to the NIC’s IP configuration.

Why this matters

In Azure, networking is modular:

  • VM
  • NIC
  • IP configuration
  • Public IP
  • NSG

Students often think these are one object, but Azure separates them into linked resources. Understanding that makes troubleshooting much easier.


Attach the NSG to the NIC

SUBNET_NSG_ID=$(az network nsg show -g $RG -n nsg-$VM --query id -o tsv)
az network nic update -g $RG -n $NIC_NAME --network-security-group nsg-$VM

What this does

The first command retrieves the NSG ID, although in this script the ID is not actually used later.

The second command attaches the NSG to the NIC.

Why this matters

NSGs can be associated with:

  • subnets
  • NICs

Here it is attached directly to the NIC, which is fine for a small lab. In larger designs, NSGs are often attached at the subnet layer to manage policy more consistently.


Get the MySQL Fully Qualified Domain Name

MYSQL_FQDN=$(az mysql flexible-server show -g $RG -n $MYSQL --query fullyQualifiedDomainName -o tsv)
echo "MySQL FQDN: $MYSQL_FQDN"

What this command does

This retrieves the MySQL server’s fully qualified domain name.

Why this is useful

The VM inside the VNet can use this hostname to connect to the database. Because the private DNS zone is linked correctly, the hostname should resolve to the private address path for the database service.

This is a key concept:

  • users and applications prefer names
  • DNS resolves names to reachable endpoints
  • private DNS makes private architectures usable

Putting It All Together

At the end of the deployment, you have:

  • a Resource Group for organization
  • a VNet for private communication
  • a VM subnet for the Windows server
  • a delegated subnet for MySQL Flexible Server
  • a private DNS zone linked to the VNet
  • a MySQL server reachable privately
  • a Windows VM that can communicate with the database
  • optional RDP access for lab management

This is a very nice learning lab because it combines both IaaS and PaaS in one environment.


Concepts Students Should Take Away

Here are the most important lessons from this setup.

Azure is built from many small connected services

Instead of one giant “server setup,” Azure uses separate resources that work together.

Private networking is a big deal

Not every application component needs internet exposure. Keeping the database private is a strong design choice.

DNS is critical

Networking is not just IP addresses. Name resolution is what makes applications practical to operate.

Managed services reduce overhead

The database is managed as a service, which means less infrastructure work than hosting MySQL on a VM.

Automation matters

Using Azure CLI makes the environment reproducible. That is a foundational cloud skill.


A Few Practical Notes About This Lab

This setup is excellent for:

  • student practice
  • certification study
  • home labs
  • proof of concept environments
  • small internal experiments

It is especially useful when you want to understand how Azure networking and managed database services fit together.

However, there are still a few things to watch for:

  • passwords in scripts are not ideal
  • open RDP from anywhere is risky
  • a single VM and single database setup is not enough for serious production resilience
  • monitoring and governance are minimal in this example

That is perfectly fine for learning. In fact, keeping the design simple helps students focus on the fundamentals first.


What We Would Add in a Real Production Environment

For a real production deployment, we would expand this design with additional Azure services and controls.

1. Azure Key Vault

We would store secrets such as:

  • VM admin credentials
  • database passwords
  • certificates
  • connection strings

Why use it here:
Hardcoding passwords in scripts is acceptable for a throwaway lab, but not for production. Key Vault protects secrets and allows controlled access.

2. Azure Bastion

We would use Azure Bastion instead of exposing RDP directly to the internet.

Why use it here:
Bastion allows secure browser-based RDP/SSH access without needing a public IP on the VM.

3. Network Security Groups with tighter rules

We would restrict source IP ranges and design subnet-level security policies carefully.

Why use it here:
Production environments should follow least privilege. Instead of allowing all internet sources, access should be tightly controlled.

4. Azure Firewall

For larger or more security-sensitive environments, Azure Firewall could provide centralized traffic control and logging.

Why use it here:
It helps govern outbound and inbound traffic across multiple subnets and workloads.

5. Microsoft Defender for Cloud

We would enable Defender for Cloud for security recommendations, posture management, and threat detection.

Why use it here:
It helps identify misconfigurations such as overly open ports, weak security settings, and missing protections.

6. Azure Monitor and Log Analytics

We would collect metrics, logs, and alerts for both the VM and the database.

Why use it here:
Production systems need visibility. Monitoring helps detect failures, slow performance, and suspicious behavior.

7. Application Insights

If the VM hosted an application, we would also add Application Insights.

Why use it here:
It provides application-level telemetry such as request rates, failures, latency, and dependency tracking.

8. Backup and disaster recovery planning

We would define backup retention, test restores, and possibly add regional recovery planning.

Why use it here:
Production systems need to survive accidental deletion, corruption, or regional failures.

9. High Availability options

We would review HA choices for the database and possibly design for multiple application instances rather than a single VM.

Why use it here:
A single VM is a single point of failure. Production workloads usually need redundancy.

10. Azure Load Balancer or Application Gateway

If the workload became a real application platform, we would likely introduce a frontend traffic distribution service.

Why use it here:
This enables scaling, resilience, and in the case of Application Gateway, layer 7 features such as TLS termination and web application firewall options.

11. Private Endpoints and broader private architecture

Depending on the full application design, we might extend private connectivity to storage accounts, Key Vault, and other PaaS services.

Why use it here:
This reduces public exposure and keeps service-to-service communication on private paths.

12. Azure Policy

We would enforce organizational standards using policy.

Why use it here:
Policies can prevent unsafe deployments, such as public IP creation where not allowed, missing tags, or unsupported SKUs.

13. Tags and governance standards

We would apply tags such as:

  • environment
  • owner
  • cost center
  • application name

Why use it here:
Tags improve reporting, automation, and cost management.

14. Infrastructure as Code with Bicep or Terraform

For production, we would usually move from ad hoc CLI commands into formal Infrastructure as Code.

Why use it here:
This improves version control, repeatability, peer review, and deployment consistency.

15. Managed identity integration for applications

Since the VM already has a managed identity, we would use it more fully with supported Azure services.

Why use it here:
This reduces dependency on stored credentials and improves security design.


Final Thoughts

This lab is a great example of how Azure services come together to build a practical environment.

You are not just creating a VM and a database. You are learning how Azure handles:

  • organization through Resource Groups
  • private networking through VNets and subnets
  • service integration through delegation
  • name resolution through Private DNS
  • managed database hosting through MySQL Flexible Server
  • secure-by-default thinking through private deployment choices

For a home lab or small student project, this setup is absolutely useful and realistic enough to teach important cloud concepts.

For production, we would layer on stronger security, monitoring, governance, and resilience services. But that does not reduce the value of a simple lab. In fact, simple labs are where strong cloud understanding begins.

If you can explain why each command in this script exists, then you are already building real Azure fundamentals.