From CLI to Cloud: Building a Simple WordPress Lab on Azure with Container Instances and MySQL

From CLI to Cloud: Building a Simple WordPress Lab on Azure with Container Instances and MySQL

One of the best ways to learn Azure is to build something small, practical, and a little bit messy on purpose. That is exactly what this lab does.

In this walkthrough, I used the Azure CLI to create a simple WordPress environment using:

  • Azure Resource Group
  • Azure Virtual Network (VNet)
  • Multiple subnets
  • Azure Database for MySQL Flexible Server
  • Azure Container Instances (ACI)
  • A Windows jumpbox VM
  • A Network Security Group (NSG)

The goal was not to create the perfect production-grade WordPress platform. The goal was to understand the building blocks of Azure cloud fundamentals by actually deploying something end to end.

This article explains the journey in tutorial style, breaking down each command, what Azure service it touches, and why that service matters.


Why this lab is useful for learning Azure fundamentals

If you are a student or someone early in cloud learning, this kind of lab is great because it introduces several core Azure concepts at once:

  • Resource organization with Resource Groups
  • Network segmentation with VNets and subnets
  • Platform services like Azure Database for MySQL
  • Containers with Azure Container Instances
  • Compute with Azure Virtual Machines
  • Access control and filtering with NSGs

You are not just deploying WordPress. You are learning how cloud services connect together.


The architecture at a high level

The setup looks like this:

  • A Resource Group named GGHomeLab contains everything
  • A Virtual Network named wp-vnet provides private IP space
  • Separate subnets are created for:
    • the WordPress container
    • the MySQL database
    • a future Application Gateway
    • a Windows VM jumpbox
  • A MySQL Flexible Server hosts the WordPress database
  • A WordPress container runs inside Azure Container Instances
  • A Windows Server VM acts as a jumpbox for administration
  • An NSG allows RDP access to the jumpbox

This is already enough to teach several important lessons: separation of concerns, service boundaries, and how Azure resources depend on each other.


Step 1: Defining variables

# Variables
RESOURCE_GROUP="GGHomeLab"
LOCATION="australiaeast"
VNET_NAME="wp-vnet"
ACI_SUBNET="aci-subnet"
DB_SUBNET="db-subnet"
APPGW_SUBNET="appgw-subnet"

Before creating resources, variables are defined in the shell.

This is not an Azure service by itself, but it is a very important automation habit. Instead of typing the same values again and again, variables make scripts:

  • easier to read
  • easier to reuse
  • less error-prone

For example, if you want to deploy the same lab in another region later, you can just change:

LOCATION="australiasoutheast"

and reuse the rest of the script.

Azure concept introduced: Region

australiaeast is the Azure region where resources will be deployed. Azure regions are geographic locations containing Microsoft datacenters.

Choosing a region affects:

  • latency
  • available services
  • pricing
  • compliance requirements

In a lab, the main idea is simply to pick a region close to you or one that supports the services you want.


Step 2: Creating a Resource Group

az group create --name $RESOURCE_GROUP --location $LOCATION

What this command does

This creates an Azure Resource Group named GGHomeLab in australiaeast.

What is a Resource Group?

A Resource Group is a logical container for Azure resources. It helps you organize related components of a solution.

In this case, the Resource Group will contain:

  • network resources
  • the MySQL server
  • the WordPress container
  • the virtual machine
  • security resources

Why it matters

Resource Groups are one of the first Azure fundamentals students should understand. They make it easier to:

  • manage a project as one unit
  • apply permissions
  • track costs
  • delete everything together when the lab is finished

For a home lab, this is especially useful because cleanup becomes simple.


Step 3: Creating a Virtual Network and first subnet

az network vnet create \
  --resource-group $RESOURCE_GROUP \
  --name $VNET_NAME \
  --address-prefix 10.0.0.0/16 \
  --subnet-name $ACI_SUBNET \
  --subnet-prefix 10.0.1.0/24

What this command does

This creates:

  • a Virtual Network called wp-vnet
  • an address space of 10.0.0.0/16
  • an initial subnet called aci-subnet
  • a subnet range of 10.0.1.0/24

What is a Virtual Network?

An Azure VNet is your private network in the cloud. It is similar to defining a private network in an on-premises environment.

It allows Azure resources to communicate privately with each other.

What is a subnet?

A subnet is a smaller network carved out of the VNet. Subnets help separate workloads.

In this lab, the first subnet is for the Azure Container Instance that will run WordPress.

Why this matters for students

This teaches one of the biggest cloud fundamentals: network isolation.

Instead of putting everything in one flat network, we divide the environment into pieces. This improves:

  • organization
  • security
  • traffic control
  • future scalability

Step 4: Adding a database subnet

az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $DB_SUBNET \
  --address-prefix 10.0.2.0/24

What this command does

This adds a second subnet named db-subnet to the VNet.

Why create a separate database subnet?

Databases should usually be separated from application workloads. In this lab:

  • WordPress runs in one subnet
  • MySQL runs in another subnet

This is a good design habit because it allows tighter control over who can access the database.

Azure concept introduced: workload segmentation

Azure networking encourages you to separate tiers of an application:

  • web tier
  • app tier
  • data tier

Even in a simple WordPress deployment, that pattern starts to appear.


Step 5: Adding a subnet for Application Gateway

az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $APPGW_SUBNET \
  --address-prefixes 10.0.3.0/24

What this command does

This creates another subnet called appgw-subnet.

Why is this interesting?

Even though the script does not yet deploy an Application Gateway, the subnet is being reserved for one.

That is a nice lesson in planning ahead. Some Azure services, such as Azure Application Gateway, are commonly deployed in dedicated subnets.

What is Azure Application Gateway?

Application Gateway is a Layer 7 load balancer for web applications. It can provide:

  • HTTP and HTTPS routing
  • SSL termination
  • cookie-based session affinity
  • Web Application Firewall features

In this lab, the subnet is a placeholder showing how infrastructure can be designed for future expansion.


Step 6: Defining MySQL variables

MYSQL_NAME="wp-mysql-flex"
MYSQL_ADMIN="mysqladmin"
MYSQL_PASS="YourStrong&Password1"
SKU="Standard_B1ms"  # Burstable for dev/test
MYSQL_DB="wordpressdb"

These variables define configuration for the Azure MySQL server.

What each variable means

  • MYSQL_NAME: name of the MySQL server
  • MYSQL_ADMIN: admin username
  • MYSQL_PASS: admin password
  • SKU: server size and performance level
  • MYSQL_DB: database name for WordPress

Azure concept introduced: SKU

Azure services often require a SKU, which defines the pricing and performance tier.

Standard_B1ms is a small burstable option, which is suitable for a lab or lightweight testing.

For students, this is a good reminder that cloud design is not just technical. It also includes cost choices.


Step 7: Creating Azure Database for MySQL Flexible Server

az mysql flexible-server create \
  --name $MYSQL_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --admin-user $MYSQL_ADMIN \
  --admin-password $MYSQL_PASS \
  --sku-name $SKU \
  --tier Burstable \
  --vnet $VNET_NAME \
  --subnet $DB_SUBNET \
  --yes

What this command does

This creates an Azure Database for MySQL Flexible Server named wp-mysql-flex and places it into the VNet using the db-subnet.

What is Azure Database for MySQL Flexible Server?

This is a managed database service. Instead of installing MySQL yourself on a VM, Azure runs and maintains the database platform for you.

Microsoft handles much of the underlying operational work such as:

  • service management
  • patching platform components
  • high availability options
  • backups
  • maintenance features

Why use it here?

WordPress needs a MySQL-compatible database. Rather than running MySQL manually in another VM or container, a managed service keeps the database layer simpler and more cloud-native.

Important parameters explained

  • --name: server name
  • --resource-group: where the server lives
  • --location: deployment region
  • --admin-user and --admin-password: credentials
  • --sku-name: compute size
  • --tier Burstable: cost-effective option for dev/test
  • --vnet and --subnet: integrate the server into the private network
  • --yes: skip prompts

Azure concept introduced: managed service

This is a major cloud principle. Instead of managing everything yourself, you consume managed services that reduce operational burden.

For beginners, this helps answer a common question:

Why cloud, instead of just VMs everywhere?

Because managed services let you focus more on the application and less on infrastructure maintenance.


Step 8: Creating the WordPress database

az mysql flexible-server db create \
  --resource-group $RESOURCE_GROUP \
  --server-name $MYSQL_NAME \
  --database-name $MYSQL_DB

What this command does

This creates a database called wordpressdb inside the MySQL Flexible Server.

Why do we need it?

The MySQL server is the database platform, but WordPress needs an actual database schema location to store:

  • posts
  • users
  • settings
  • themes and plugin metadata

This step prepares that database container for the application.

Azure concept introduced: server vs database

Students often confuse these two ideas:

  • server = the MySQL service instance
  • database = the actual logical database inside that server

This command shows that both layers exist.


Step 9: Defining container variables

ACI_NAME="wp-container"
IMAGE="wordpress:latest"
MYSQL_HOST="wp-mysql-flex.mysql.database.azure.com"

These variables prepare the container deployment.

What they mean

  • ACI_NAME: the name of the container group
  • IMAGE: the Docker image to run
  • MYSQL_HOST: the MySQL server FQDN

Azure concept introduced: container image

wordpress:latest is a public container image, typically pulled from Docker Hub.

A container image packages the application and its dependencies. Instead of installing WordPress manually on a server, the image already contains the software needed to run it.

This is one reason containers are so popular in modern cloud environments.


Step 10: Creating the WordPress container in Azure Container Instances

az container create \
  --resource-group $RESOURCE_GROUP \
  --name $ACI_NAME \
  --os-type="Linux" \
  --image $IMAGE \
  --vnet $VNET_NAME \
  --subnet $ACI_SUBNET \
  --ports 80 \
  --cpu 1 \
  --memory 1.5 \
  --environment-variables \
    WORDPRESS_DB_HOST=$MYSQL_HOST \
    WORDPRESS_DB_NAME=$MYSQL_DB \
    WORDPRESS_DB_USER=$MYSQL_ADMIN \
    WORDPRESS_DB_PASSWORD=$MYSQL_PASS \
  --restart-policy Always

What this command does

This deploys a Linux container running WordPress into Azure Container Instances and connects it to the VNet subnet created earlier.

What is Azure Container Instances?

ACI is a service for running containers without managing servers or orchestrators.

It is useful when you want to run a container quickly without:

  • building a Kubernetes cluster
  • managing a VM host
  • dealing with complex orchestration

Why ACI is good for a learning lab

For students, ACI is a nice middle ground:

  • more modern than deploying everything on a VM
  • simpler than learning AKS on day one
  • fast to provision
  • suitable for experiments and small workloads

Key parameters explained

  • --os-type="Linux": the container runs on Linux
  • --image $IMAGE: use the WordPress container image
  • --vnet and --subnet: attach the container to the private network
  • --ports 80: expose HTTP
  • --cpu 1: allocate 1 vCPU
  • --memory 1.5: allocate 1.5 GB RAM
  • --environment-variables: pass database settings to WordPress
  • --restart-policy Always: restart the container if it stops

Why the environment variables matter

WordPress needs to know how to reach its database. These values tell it:

  • where the database server is
  • which database to use
  • what username and password to connect with

This is how the application tier gets connected to the data tier.

A practical note

In a real design, storing passwords directly in the CLI command is not ideal. It works for a lab, but production would use a secrets solution such as Azure Key Vault.


Step 11: Defining variables for the jumpbox VM

ADMIN_USER="azureuser"
ADMIN_PASS="YourStrong&Password123"  # Must meet complexity rules
IMAGE="Win2022Datacenter"  # Use latest Windows Server
VM_NAME="wp-jumpbox"
VM_SUBNET="vm-subnet"

These variables prepare a Windows virtual machine deployment.

What is a jumpbox?

A jumpbox, sometimes called a bastion host, is an administrative VM used to access resources in a private network.

In this lab, the jumpbox can be used to:

  • log in remotely
  • test connectivity
  • administer resources from inside the network

Azure concept introduced: Infrastructure as a Service

Unlike ACI or MySQL Flexible Server, a VM is more traditional IaaS. You get an operating system to manage yourself.

This is useful for learning because it contrasts nicely with managed services.


Step 12: Looking up a subnet ID

# Get subnet ID
SUBNET_ID=$(az network vnet subnet show \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $ACI_SUBNET \
  --query id -o tsv)

What this command does

This retrieves the Azure resource ID of the aci-subnet and stores it in a shell variable called SUBNET_ID.

Why this is useful

Many Azure commands can work with either:

  • names
  • full resource IDs

A resource ID is the full unique path of an Azure resource.

Even though this variable is not used later in the script, it is still a useful example because students often encounter resource IDs in automation, ARM templates, Bicep, Terraform, and CLI scripting.

Azure concept introduced: resource ID

Azure resources have unique IDs that describe where they live in the subscription hierarchy.

These IDs become very important in larger automation workflows.


Step 13: Creating a subnet for the virtual machine

az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $VM_SUBNET \
  --address-prefix 10.0.4.0/24

What this command does

This adds another subnet named vm-subnet for the jumpbox VM.

Why separate the VM?

Again, this is about segmentation and good design habits.

Having a dedicated subnet for admin systems makes it easier later to:

  • apply NSGs
  • control access rules
  • monitor admin traffic separately

Even in a small lab, practicing separation helps build production thinking.


Step 14: Creating a Network Interface Card for the VM

az network nic create \
  --resource-group $RESOURCE_GROUP \
  --name ${VM_NAME}-nic \
  --vnet-name $VNET_NAME \
  --subnet $VM_SUBNET

What this command does

This creates a Network Interface Card (NIC) for the VM in the vm-subnet.

What is a NIC in Azure?

Just like a physical or virtual network adapter in traditional environments, an Azure NIC connects a VM to a network.

It can hold:

  • private IP settings
  • public IP association
  • NSG association
  • IP forwarding settings

Why create it separately?

Creating the NIC separately gives you more control. You can attach security settings and manage networking independently from the VM creation process.

This is a valuable lesson because cloud resources are often modular.


Step 15: Creating the Windows virtual machine

az vm create \
  --resource-group $RESOURCE_GROUP \
  --name $VM_NAME \
  --image $IMAGE \
  --admin-username $ADMIN_USER \
  --admin-password $ADMIN_PASS \
  --nics ${VM_NAME}-nic \
  --size "Standard_D2as_v5"

What this command does

This creates a Windows Server 2022 VM named wp-jumpbox using the NIC created earlier.

Azure service involved

This uses Azure Virtual Machines, one of the core compute services in Azure.

Key parameters explained

  • --image $IMAGE: deploy Windows Server 2022 Datacenter
  • --admin-username and --admin-password: local VM credentials
  • --nics: attach the pre-created NIC
  • --size "Standard_D2as_v5": VM size and performance characteristics

Why this matters in cloud fundamentals

Students should understand that Azure offers different compute models:

  • VMs for full control
  • Containers for lightweight packaged apps
  • Managed PaaS services for reduced operational overhead

This lab includes all three styles in one solution, which makes it a great teaching exercise.


Step 16: Creating a Network Security Group

NSG_NAME="wp-jumpbox-nsg"
RULE_NAME="Allow-RDP"
SOURCE_PORT_PREFIX="0.0.0.0/0"

# Create NSG
az network nsg create \
  --resource-group $RESOURCE_GROUP \
  --name $NSG_NAME

What this command does

This creates a Network Security Group called wp-jumpbox-nsg.

What is an NSG?

An NSG is a packet filtering firewall for Azure network traffic. It can be associated with:

  • a subnet
  • a NIC

NSGs contain rules that allow or deny inbound and outbound traffic.

Why it matters

Security groups are a core Azure networking concept. They let you control who can talk to what.

In this case, the NSG will be used to allow Remote Desktop access to the jumpbox VM.


Step 17: Adding an inbound RDP rule

az network nsg rule create \
  --resource-group $RESOURCE_GROUP \
  --nsg-name $NSG_NAME \
  --name $RULE_NAME \
  --protocol Tcp \
  --direction Inbound \
  --priority 1000 \
  --source-address-prefixes $SOURCE_PORT_PREFIX \ 
  --source-port-ranges "*" \
  --destination-address-prefixes "*" \
  --destination-port-ranges 3389 \
  --access Allow

What this command does

This adds an NSG rule that allows inbound TCP traffic on port 3389, which is the default port for RDP.

What each important parameter means

  • --protocol Tcp: RDP uses TCP
  • --direction Inbound: this rule applies to incoming traffic
  • --priority 1000: lower number means higher priority
  • --source-address-prefixes: defines who can connect
  • --destination-port-ranges 3389: target RDP port
  • --access Allow: permit the traffic

Important learning point

The variable name SOURCE_PORT_PREFIX is a little misleading here because the value 0.0.0.0/0 is actually used as a source address range, not a source port. That CIDR means any IP address.

So effectively, this rule allows RDP from anywhere on the internet.

Security warning

This may be acceptable in a temporary lab, but it is not recommended for real environments.

Better choices would include:

  • restricting RDP to your own public IP
  • using Azure Bastion
  • using Just-In-Time VM access
  • avoiding direct exposure altogether

Small script note

There is also an extra space after the line continuation on:

--source-address-prefixes $SOURCE_PORT_PREFIX \ 

In a shell script, that stray space after the backslash can cause problems. It is better written as:

--source-address-prefixes $SOURCE_PORT_PREFIX \

This is a nice reminder that cloud automation depends not only on architecture knowledge, but also on careful scripting.


Step 18: Associating the NSG with the VM NIC

NIC_NAME="${VM_NAME}-nic"  
az network nic update \
  --resource-group $RESOURCE_GROUP \
  --name $NIC_NAME \
  --network-security-group $NSG_NAME

What this command does

This attaches the NSG to the VM's NIC.

Why this matters

Once attached, the NIC now follows the NSG rules, including the RDP allow rule.

This means the VM's network access is being controlled by the NSG.

Azure concept introduced: scope of security controls

NSGs can be attached at different levels:

  • subnet level
  • NIC level

In this script, the NSG is attached at the NIC level, which gives control over this specific VM.


What this lab teaches overall

By the end of this setup, a student is exposed to several major Azure concepts:

1. Resource organization

The Resource Group keeps all related resources together.

2. Cloud networking

The VNet and subnets model a private cloud network.

3. Segmentation

Different subnets are used for app, database, future gateway, and admin access.

4. Managed database services

Azure Database for MySQL Flexible Server reduces infrastructure management effort.

5. Container-based app hosting

Azure Container Instances runs WordPress without needing a full VM.

6. Traditional VM administration

The jumpbox VM shows the IaaS model and remote administration pattern.

7. Security controls

NSGs demonstrate basic network traffic filtering.

That is a lot of cloud fundamentals from one small WordPress project.


What could be improved even in a lab

This setup works as a learning exercise, but there are some things worth calling out:

  • passwords are written directly in the script
  • the WordPress image uses latest, which is not ideal for repeatable deployments
  • RDP is open from anywhere
  • no TLS or HTTPS is configured
  • no persistent shared storage is configured for WordPress content
  • no backup or disaster recovery story is defined for the application tier
  • no monitoring or logging is configured in a serious way

These are not failures. They are actually useful discussion points for students because they show the difference between lab design and production design.


For a real production environment, what else would we add?

This lab is fine for a home lab, student exercise, or a very small proof of concept. But in a real production environment, we would usually incorporate more Azure services to improve security, availability, scalability, and operations.

Here are some of the most important ones and why they would matter in this scenario.

Azure Key Vault

We would use Azure Key Vault to store secrets such as:

  • MySQL passwords
  • admin credentials
  • TLS certificates
  • API keys

This avoids hardcoding sensitive values in scripts or environment variables.

Azure Application Gateway

We would use Azure Application Gateway in front of WordPress to provide:

  • Layer 7 load balancing
  • HTTPS termination
  • smarter routing
  • better control of web traffic

Since an appgw-subnet was already planned, this is a natural next step.

Web Application Firewall (WAF)

We would enable WAF, typically with Application Gateway, to protect the WordPress site from common web attacks such as:

  • SQL injection attempts
  • cross-site scripting
  • malicious request patterns

WordPress is a common internet-facing target, so this becomes very important.

Azure Bastion

Instead of exposing RDP to the internet, we would use Azure Bastion for secure browser-based administration of the VM without opening port 3389 publicly.

This greatly reduces attack surface.

Azure Monitor and Log Analytics

We would use Azure Monitor and Log Analytics to collect:

  • metrics
  • logs
  • alerts
  • performance data

In production, visibility is essential. You need to know when the app is slow, when the database is stressed, or when login attempts look suspicious.

Azure Container Registry

Instead of pulling wordpress:latest from a public registry, we would often use Azure Container Registry (ACR) to store trusted, versioned images.

This provides:

  • more control
  • better supply chain hygiene
  • version stability
  • easier integration with CI/CD pipelines

Azure Files or Azure NetApp Files

WordPress often needs persistent storage for uploads and media. In production, we would use a persistent storage solution rather than relying on ephemeral container storage.

This helps preserve user content across restarts or redeployments.

Azure Backup

Although MySQL Flexible Server has backup capabilities, we would also think carefully about backup and recovery strategy for the full application.

Production systems need documented recovery plans, not just hope.

Azure Front Door

For public-facing global applications, Azure Front Door can provide:

  • global entry point
  • performance optimization
  • SSL offload
  • DDoS-aware edge routing
  • failover across regions

This is especially useful if the site must be reachable quickly and reliably from multiple geographic locations.

DDoS Protection

For internet-facing workloads, Azure DDoS Protection may be added to strengthen resilience against denial-of-service attacks.

Azure Policy

We would use Azure Policy to enforce governance rules, such as:

  • only approved regions
  • required tags
  • encrypted resources
  • disallowing public IPs on sensitive workloads

This helps keep environments consistent and compliant.

Microsoft Defender for Cloud

We would use Microsoft Defender for Cloud to improve security posture, detect weak configurations, and provide recommendations across the environment.

Availability and scale services

If this became a serious application, we would likely rethink the hosting layer too.

Instead of ACI alone, we might move to:

  • Azure Kubernetes Service (AKS) for orchestration
  • Azure App Service for Containers for managed web hosting
  • multiple instances behind a load balancer for high availability

ACI is simple and good for labs, but it is not usually the final answer for production-scale WordPress hosting.


Final thoughts

I like this lab because it tells a real cloud story.

It starts simply: create a resource group, define a network, deploy a database, run a container, add a jumpbox, and open remote access. But inside those steps are many of the most important Azure cloud fundamentals.

You learn that cloud is not just about creating a server. It is about thinking in layers:

  • organization
  • networking
  • security
  • compute
  • managed services
  • future scalability

And that is exactly why hands-on labs matter.

A small WordPress deployment may not look glamorous, but it is a great teaching platform. It exposes the difference between something that merely works and something that is designed well.

For a student, that is the real win.


Full command reference used in this lab

For convenience, here is the original flow in logical order:

# Variables
RESOURCE_GROUP="GGHomeLab"
LOCATION="australiaeast"
VNET_NAME="wp-vnet"
ACI_SUBNET="aci-subnet"
DB_SUBNET="db-subnet"
APPGW_SUBNET="appgw-subnet"

# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION

# Create VNet with first subnet
az network vnet create \
  --resource-group $RESOURCE_GROUP \
  --name $VNET_NAME \
  --address-prefix 10.0.0.0/16 \
  --subnet-name $ACI_SUBNET \
  --subnet-prefix 10.0.1.0/24

# Add DB subnet
az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $DB_SUBNET \
  --address-prefix 10.0.2.0/24

# Add subnet reserved for Application Gateway
az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $APPGW_SUBNET \
  --address-prefixes 10.0.3.0/24

MYSQL_NAME="wp-mysql-flex"
MYSQL_ADMIN="mysqladmin"
MYSQL_PASS="YourStrong&Password1"
SKU="Standard_B1ms"
MYSQL_DB="wordpressdb"

az mysql flexible-server create \
  --name $MYSQL_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --admin-user $MYSQL_ADMIN \
  --admin-password $MYSQL_PASS \
  --sku-name $SKU \
  --tier Burstable \
  --vnet $VNET_NAME \
  --subnet $DB_SUBNET \
  --yes

az mysql flexible-server db create \
  --resource-group $RESOURCE_GROUP \
  --server-name $MYSQL_NAME \
  --database-name $MYSQL_DB

ACI_NAME="wp-container"
IMAGE="wordpress:latest"
MYSQL_HOST="wp-mysql-flex.mysql.database.azure.com"

az container create \
  --resource-group $RESOURCE_GROUP \
  --name $ACI_NAME \
  --os-type="Linux" \
  --image $IMAGE \
  --vnet $VNET_NAME \
  --subnet $ACI_SUBNET \
  --ports 80 \
  --cpu 1 \
  --memory 1.5 \
  --environment-variables \
    WORDPRESS_DB_HOST=$MYSQL_HOST \
    WORDPRESS_DB_NAME=$MYSQL_DB \
    WORDPRESS_DB_USER=$MYSQL_ADMIN \
    WORDPRESS_DB_PASSWORD=$MYSQL_PASS \
  --restart-policy Always

ADMIN_USER="azureuser"
ADMIN_PASS="YourStrong&Password123"
IMAGE="Win2022Datacenter"
VM_NAME="wp-jumpbox"
VM_SUBNET="vm-subnet"

SUBNET_ID=$(az network vnet subnet show \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $ACI_SUBNET \
  --query id -o tsv)

az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $VM_SUBNET \
  --address-prefix 10.0.4.0/24

az network nic create \
  --resource-group $RESOURCE_GROUP \
  --name ${VM_NAME}-nic \
  --vnet-name $VNET_NAME \
  --subnet $VM_SUBNET

az vm create \
  --resource-group $RESOURCE_GROUP \
  --name $VM_NAME \
  --image $IMAGE \
  --admin-username $ADMIN_USER \
  --admin-password $ADMIN_PASS \
  --nics ${VM_NAME}-nic \
  --size "Standard_D2as_v5"

NSG_NAME="wp-jumpbox-nsg"
RULE_NAME="Allow-RDP"
SOURCE_PORT_PREFIX="0.0.0.0/0"

az network nsg create \
  --resource-group $RESOURCE_GROUP \
  --name $NSG_NAME

az network nsg rule create \
  --resource-group $RESOURCE_GROUP \
  --nsg-name $NSG_NAME \
  --name $RULE_NAME \
  --protocol Tcp \
  --direction Inbound \
  --priority 1000 \
  --source-address-prefixes $SOURCE_PORT_PREFIX \
  --source-port-ranges "*" \
  --destination-address-prefixes "*" \
  --destination-port-ranges 3389 \
  --access Allow

NIC_NAME="${VM_NAME}-nic"
az network nic update \
  --resource-group $RESOURCE_GROUP \
  --name $NIC_NAME \
  --network-security-group $NSG_NAME

Happy lab building.