From Zero to Live: Deploying a Dockerized Website on Azure with Cloud-Init Automation
A beginner's honest walkthrough of provisioning a cloud VM, automating Docker installation, and serving a static site from a container

Let me set the scene.
It is April 2026. I have been learning DevOps since January. I have watched more YouTube videos than I care to admit, read documentation until my eyes glazed over, and pushed through more than a few moments of "I have absolutely no idea what I am doing."
This week, everything clicked.
My Week 14 project for the DevOps Micro Internship was to provision an Azure VM, use cloud-init to automatically install Docker on boot, containerize a static website using Nginx, and serve it live on the internet.
I want to walk you through it properly, because if you are learning DevOps and containers still feel like a mystery, I want this post to be the one that makes them make sense.
First, Let Me Explain Docker Like You Are New Here
Because I was new here not too long ago.
Docker lets you package your application and everything it needs into a single portable box called a container. That container runs exactly the same way on any machine, whether it is your laptop, a cloud server, or your colleague's computer across the world.
Here are the three terms you need to know:
Image — the blueprint. A set of instructions for building your container. Think of it like a recipe.
Container — the running instance of that image. The actual cooked meal.
Dockerfile — the file where you write your image instructions. One line at a time.
Got it? Good. Let's build something.
The Architecture
Here is what we are building:
Your Browser
|
| HTTP on port 80
v
Azure VM (Ubuntu 24.04 LTS)
Public IP: 4.234.163.212
|
| Port mapping 0.0.0.0:80 -> container port 80
v
Docker Container
|
v
Nginx (serving your static HTML)
Simple, clean, and real.
Provisioning the Azure VM
I created the VM through the Azure Portal. The key settings:
Image: Ubuntu 24.04 LTS
Size: Standard D2lds v6 (2 vCPUs, 4 GiB RAM)
Region: UK South
NSG Rules: Port 22 open for SSH, Port 80 open for HTTP
The networking part is important. If port 80 is not open in your Network Security Group, your site will never be reachable from the browser no matter what you do inside the VM.
The Part That Changed How I Think About Infrastructure
Before clicking Create, I went to the Advanced tab and pasted a cloud-init script into the Custom Data field.
This is what it looked like:
#cloud-config
package_update: true
package_upgrade: true
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
runcmd:
- apt-get update -y
- apt-get install -y docker.io
- systemctl enable docker
- systemctl start docker
- usermod -aG docker azureuser
When the VM booted for the first time, this script ran automatically. Docker installed itself. The Docker service started itself. My user was added to the docker group.
I had not logged in yet.
That is the moment infrastructure automation stopped being something I read about and became something I actually understood. You write your intentions once, and the machine carries them out every single time, without you standing over it.
Later I confirmed it by running:
sudo cat /var/log/cloud-init-output.log | grep -i docker
The log was right there. Every line of the Docker installation, timestamped, proving the script did exactly what I told it to do.
Building and Running the Container
After SSH-ing into the VM, I cloned the static site repo:
git clone https://github.com/pravinmishraaws/Azure-Static-Website.git
cd Azure-Static-Website
Then I wrote the Dockerfile:
FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY . /usr/share/nginx/html
EXPOSE 80
Built the image:
docker build -t static-site:latest .
Ran the container:
docker run -d --name static-site \
-p 80:80 \
--restart unless-stopped \
static-site:latest
Verified it was running:
docker ps
# 0.0.0.0:80->80/tcp — static-site is alive
Then opened http://4.234.163.212 in my browser.
The site loaded.
I sat there for a moment just looking at it. Elated and fulfilled are the exact words I would use. Something I built, running live on the internet, inside a container I created, on a VM I provisioned.
That feeling is why I started this journey.
Key Things I Will Not Forget
Cloud-init is how you scale. When you need to spin up 10 or 100 VMs, you cannot manually install Docker on each one. Cloud-init is how teams automate this at scale.
nginx:alpine is lean by design. The alpine variant of Nginx is stripped down to only what it needs. Our final image was 92.9MB. For a production static site, that is clean.
The --restart flag is not optional in production. If your VM ever reboots, your container will come back up automatically. Always include it.
Port mapping is the bridge. The -p 80:80 flag is what connects the outside world to your container. Without it, your container is running but invisible.
What Is Next
This is Week 14 of my DevOps Micro Internship. Next up is multi-stage Docker builds for a React app, and then a full capstone project deploying a production application with Docker Compose, healthchecks, volumes, and a reverse proxy.
I will document all of it.
GitHub: https://github.com/vivianokose/cloud-vm-docker-deploy
If you are somewhere at the beginning of your DevOps journey, keep going. The day your first container serves a live website, you will understand why.




