2. Deploy Docker Containers from Docker Hub to Azure VM
๐ฏ Goal
Learn how to provision an Azure Virtual Machine with Docker pre-installed, then deploy containers from Docker Hub including both public images (nginx) and your own containerized applications from the previous exercise.
๐ Prerequisites
Before beginning this exercise, you should:
- Have an Azure subscription with credits available
- Have Azure CLI installed locally (version 2.0 or higher)
- Have completed the previous Docker exercise (PHP application pushed to Docker Hub with multi-architecture support)
- Have basic understanding of Linux command line
- Have SSH client available (built-in on Mac/Linux, use PowerShell on Windows)
โ ๏ธ Note: If you haven’t built your Docker image with multi-architecture support, refer to Step 3 in the previous tutorial for instructions using
docker buildx
๐ Learning Objectives
By the end of this exercise, you will:
- Provision an Azure VM with Docker pre-installed using both Portal and CLI methods
- Configure network security rules to allow web traffic on multiple ports
- Deploy public Docker images from Docker Hub (nginx)
- Deploy your own containerized application from Docker Hub
- Understand how cloud-init scripts automate VM configuration
๐ Why This Matters
In real-world scenarios, deploying containers to cloud VMs is crucial because:
- It provides a cost-effective way to run containerized applications in the cloud
- It enables rapid deployment and scaling of applications
- It’s a stepping stone to understanding container orchestration (Kubernetes, ACI)
- It combines infrastructure management with modern containerization practices
๐ Step-by-Step Instructions
Step 1: Provision an Azure VM with Docker
You can choose either the Azure Portal method (visual) or Azure CLI method (command line). Both achieve the same result.
Option A: Using Azure Portal
Login to Azure Portal
- Navigate to https://portal.azure.com
- Sign in with your Azure credentials
Create a Resource Group
- Click “Resource groups” in the left menu
- Click “+ Create”
- Fill in:
- Subscription: Select your subscription
- Resource group:
DockerDemoRG - Region:
North Europe
- Click “Review + create” then “Create”
Create the Virtual Machine
- Click “Create a resource” โ “Compute” โ “Virtual machine”
- Basics tab:
- Resource group: Select
DockerDemoRG - Virtual machine name:
DockerVM - Region:
North Europe - Image:
Ubuntu Server 22.04 LTS - Size:
Standard_B1s(1 vcpu, 1 GiB memory) - Authentication type:
SSH public key - Username:
azureuser - SSH public key source: Generate new key pair
- Key pair name:
DockerVM_key
- Resource group: Select
Configure Networking
- Click “Next: Disks” โ “Next: Networking”
- Public inbound ports: Select “Allow selected ports”
- Select inbound ports: Check
SSH (22),HTTP (80),HTTPS (443) - Add a new inbound port rule for port 8080:
- After VM creation, go to “Networking” โ “Add inbound port rule”
- Destination port ranges:
8080 - Name:
Port_8080
Add Custom Data (Cloud-init)
- Click “Next: Management” โ “Next: Monitoring” โ “Next: Advanced”
- In the Custom data field, paste:
#!/bin/bash # Add Docker's official GPG key apt-get update apt-get install -y ca-certificates curl install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker Engine apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # Add azureuser to docker group usermod -aG docker azureuserCreate the VM
- Click “Review + create”
- Review the settings
- Click “Create”
- Download the private key when prompted
๐ก Information
- Cloud-init: Runs once when the VM first boots to configure the system
- Standard_B1s: Most cost-effective size for testing (eligible for free tier)
- SSH Key: More secure than password authentication
- The VM creation takes 3-5 minutes to complete
Option B: Using Azure CLI
Ensure you have the cloud-init script ready:
Create a file named
cloud-init_docker.shwith the Docker installation commands:cat > cloud-init_docker.sh << 'EOF' #!/bin/bash # Add Docker's official GPG key apt-get update apt-get install -y ca-certificates curl install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker Engine apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # Add azureuser to docker group usermod -aG docker azureuser EOFRun the provisioning script:
# Set variables RESOURCE_GROUP="DockerDemoRG" LOCATION="northeurope" VM_NAME="DockerVM" # Create resource group az group create --name $RESOURCE_GROUP --location $LOCATION # Create VM with Docker az vm create \ --resource-group $RESOURCE_GROUP \ --location $LOCATION \ --name $VM_NAME \ --image Ubuntu2204 \ --size Standard_B1s \ --admin-username azureuser \ --generate-ssh-keys \ --custom-data @cloud-init_docker.sh # Open required ports az vm open-port --port 80 --resource-group $RESOURCE_GROUP --name $VM_NAME az vm open-port --port 8080 --resource-group $RESOURCE_GROUP --name $VM_NAME --priority 1001Get the public IP address:
az vm show --resource-group $RESOURCE_GROUP --name $VM_NAME \ --show-details --query [publicIps] --output tsv
๐ก Information
- –generate-ssh-keys: Creates SSH keys if they don’t exist (~/.ssh/id_rsa)
- –custom-data: Passes the cloud-init script to configure Docker
- –priority: NSG rules need unique priorities (100-4096)
- The script takes about 5 minutes to complete
Step 2: Connect to the VM and Verify Docker Installation
Get your VM’s public IP address:
# If using CLI az vm list-ip-addresses --resource-group DockerDemoRG --name DockerVM --output tableOr find it in the Azure Portal under your VM’s Overview page.
SSH into the VM:
ssh azureuser@<YOUR_VM_PUBLIC_IP>If you downloaded a private key from the Portal:
ssh -i ~/Downloads/DockerVM_key.pem azureuser@<YOUR_VM_PUBLIC_IP>Wait for cloud-init to complete (important!):
ssh azureuser@<YOUR_VM_PUBLIC_IP> "cloud-init status --wait"This ensures Docker installation is complete before proceeding.
Verify Docker is installed:
docker --versionExpected output:
Docker version 27.x or higher, build xxxxxxxTest Docker without sudo:
docker run hello-worldIf you get a permission error, logout and login again for group changes to take effect:
exit ssh azureuser@<YOUR_VM_PUBLIC_IP>
๐ก Information
- First SSH connection: May take 30-60 seconds as the VM finalizes setup
- Cloud-init: Runs asynchronously - always wait for it to complete before testing Docker
- Group membership: The azureuser needs to logout/login for docker group to be active
- Cloud-init logs: Check
/var/log/cloud-init-output.logif Docker isn’t installed
Step 3: Deploy nginx Container on Port 80
Pull and run the official nginx container:
docker run -d \ --name nginx-web \ -p 80:80 \ nginx:latestVerify the container is running:
docker psExpected output:
CONTAINER ID IMAGE COMMAND PORTS NAMES abc123def456 nginx:latest "/docker-entrypoint..." 0.0.0.0:80->80/tcp nginx-webTest nginx from the VM:
curl localhostYou should see HTML output from nginx.
Test from your browser:
Open:
http://<YOUR_VM_PUBLIC_IP>You should see the nginx welcome page.
๐ก Information
- -d: Runs container in detached mode (background)
- -p 80:80: Maps VM’s port 80 to container’s port 80
- –name: Gives the container a friendly name for management
- nginx serves a default welcome page on fresh installation
โ ๏ธ Common Issues
- If the page doesn’t load, verify port 80 is open in the Network Security Group
- Check if another service is using port 80:
sudo netstat -tulpn | grep :80
Step 4: Deploy Your PHP Application from Docker Hub
Now let’s deploy the PHP application you containerized in the previous exercise.
Run your PHP application container:
Replace
yourusernamewith your Docker Hub username:docker run -d \ --name php-app \ --platform linux/amd64 \ -p 8080:8000 \ yourusername/php-contact-app:latest๐ก Important: The
--platform linux/amd64flag ensures compatibility with Azure VMsFor example, if you followed the previous tutorial:
docker run -d \ --name php-app \ --platform linux/amd64 \ -p 8080:8000 \ larsappel/php-contact-app:latestVerify both containers are running:
docker psYou should see both nginx and php-app containers:
CONTAINER ID IMAGE PORTS NAMES def456ghi789 yourusername/php-contact-app 0.0.0.0:8080->8000/tcp php-app abc123def456 nginx:latest 0.0.0.0:80->80/tcp nginx-webTest the PHP application:
From the VM:
curl localhost:8080From your browser:
- nginx:
http://<YOUR_VM_PUBLIC_IP> - PHP app:
http://<YOUR_VM_PUBLIC_IP>:8080
- nginx:
Check container logs if needed:
docker logs php-app
๐ก Information
- Port mapping: VM port 8080 โ Container port 8000
- Public images: Docker automatically pulls from Docker Hub if not found locally
- Multiple containers: Can run many containers on different ports
- Each container is isolated but shares the VM’s kernel
Step 5: Container Management
Learn essential Docker commands for managing your deployed containers:
View running containers:
docker psView all containers (including stopped):
docker ps -aStop a container:
docker stop nginx-web docker stop php-appStart a stopped container:
docker start nginx-web docker start php-appView container resource usage:
docker statsPress
Ctrl+Cto exit.View container logs:
# View last 50 lines docker logs --tail 50 php-app # Follow logs in real-time docker logs -f nginx-web
๐ก Information
- Container lifecycle: Containers can be stopped/started without losing data
- Logs: Essential for debugging - containers run in the background
- Stats: Monitor CPU and memory usage of containers
๐งช Final Tests
Validate Your Complete Deployment
Check all services are accessible:
# From your local machine curl -I http://<YOUR_VM_PUBLIC_IP> # Should return HTTP/1.1 200 OK curl -I http://<YOUR_VM_PUBLIC_IP>:8080 # Should return HTTP/1.1 200 OKTest the PHP application functionality:
# Test form submission curl -X POST \ -d "name=Azure Test&email=test@azure.com&message=Hello from Azure VM!" \ http://<YOUR_VM_PUBLIC_IP>:8080/process_contact_form.phpVerify containers restart after VM reboot:
# Add restart policy to containers docker update --restart unless-stopped nginx-web docker update --restart unless-stopped php-app # Reboot VM (from Azure Portal or CLI) az vm restart --resource-group DockerDemoRG --name DockerVM
โ Expected Results
- nginx welcome page loads on port 80
- PHP application loads on port 8080
- Contact form submissions work correctly
- Both containers are running simultaneously
- Containers automatically restart after VM reboot
๐ง Troubleshooting
Common issues and solutions:
Port Access Issues
- Cannot access website from browser
- Verify Network Security Group rules in Azure Portal
- Check if containers are running:
docker ps - Test locally first:
curl localhost:80
Docker Permission Denied
Error: “permission denied while trying to connect to Docker daemon”
Solution:
# Logout and login again exit ssh azureuser@<YOUR_VM_PUBLIC_IP>
Container Won’t Start
Check logs:
docker logs <container-name>Check port conflicts:
docker psandsudo netstat -tulpnRemove and recreate:
docker rm -f <container-name> docker run -d ...
VM Performance Issues
- Standard_B1s is limited: Consider upgrading to Standard_B2s for production
- Check resource usage:
docker statsandtop
๐งน Clean Up
After completing the exercise, clean up to avoid charges:
Option A: Clean Up via Portal
- Navigate to Resource Groups
- Select
DockerDemoRG - Click “Delete resource group”
- Type the resource group name to confirm
- Click “Delete”
Option B: Clean Up via CLI
# Delete the entire resource group
az group delete --name DockerDemoRG --yes --no-wait
Keep Containers, Delete VM Only
If you want to practice locally with Docker:
# Save container images locally first
docker save yourusername/php-contact-app:latest -o php-app.tar
docker save nginx:latest -o nginx.tar
# Then delete Azure resources
az group delete --name DockerDemoRG --yes
๐ Optional Challenge
Want to extend your learning? Try these challenges:
Add SSL/HTTPS with Let’s Encrypt:
- Run nginx-proxy with automatic SSL
- Configure your containers to work with HTTPS
Deploy a Database Container:
- Add MySQL or PostgreSQL container
- Connect your PHP app to the database
- Use Docker networks for container communication
Implement Docker Compose:
- Create a
docker-compose.ymlfor both services - Deploy the entire stack with one command
- Create a
Set Up Monitoring:
- Deploy Prometheus and Grafana containers
- Monitor your containers’ performance
Automate with Azure DevOps:
- Create a pipeline to deploy containers automatically
- Implement continuous deployment from Docker Hub
๐ Further Reading
- Azure Virtual Machines Documentation
- Docker on Azure Documentation
- Azure Container Instances - Serverless containers
- Azure Kubernetes Service - Next step in container orchestration
Done! ๐
Excellent work! You’ve successfully deployed Docker containers from Docker Hub to an Azure VM. You now understand how to:
- โ Provision cloud infrastructure with automated configuration
- โ Deploy multiple containers on different ports
- โ Manage containers in a cloud environment
- โ Combine IaaS (VMs) with containerization
This foundation prepares you for more advanced topics like container orchestration with Kubernetes and serverless containers with Azure Container Instances! ๐
Your deployed application demonstrates the power of combining cloud infrastructure with containerization - a critical skill for modern cloud engineering!