DEV Community

Cover image for How I Deployed a Live Website Using Docker on Azure (And Let Cloud-Init Do the Heavy Lifting)
Vivian Chiamaka Okose
Vivian Chiamaka Okose

Posted on • Originally published at vivianokose.hashnode.dev

How I Deployed a Live Website Using Docker on Azure (And Let Cloud-Init Do the Heavy Lifting)

What I Built

  • 1 Linux VM on Azure (Ubuntu 24.04 LTS, Standard D2lds v6)
  • A cloud-init script that installed Docker automatically on first boot
  • A Dockerized static website served by Nginx
  • A live URL accessible from anywhere in the world

Step 1: Provisioning the Azure VM

I created the VM through the Azure Portal with these settings:

Field Value
Resource Group docker-project-rg
VM Name docker-vm
Region UK South
Image Ubuntu 24.04 LTS
Size Standard D2lds v6
Authentication SSH public key

Under the Networking tab, I opened port 22 for SSH and port 80 for HTTP traffic by adding inbound rules to the Network Security Group.


Step 2: The Cloud-Init Script (This Is the Magic Part)

Before launching the VM, I pasted this script under the Advanced tab in the Custom Data field:

#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
Enter fullscreen mode Exit fullscreen mode

This script runs automatically the moment the VM boots. Docker installs itself, starts itself, and adds my user to the docker group. I had not even SSH'd in yet.

When I later ran:

sudo cat /var/log/cloud-init-output.log | grep -i docker
Enter fullscreen mode Exit fullscreen mode

The log confirmed everything. Docker was installed by the startup script, not by me. That is infrastructure automation doing exactly what it is supposed to do.


Step 3: SSH Into the VM

Once deployment completed, I grabbed my public IP from the Azure Portal and connected:

chmod 400 docker-vm_key.pem
ssh -i docker-vm_key.pem azureuser@4.234.163.212
Enter fullscreen mode Exit fullscreen mode

Then I verified Docker was running:

docker --version
# Docker version 29.1.3

docker ps
# Empty, no containers yet. But Docker is alive.
Enter fullscreen mode Exit fullscreen mode

Step 4: Clone the Static Website

git clone https://github.com/pravinmishraaws/Azure-Static-Website.git
cd Azure-Static-Website
ls
# README.md  index.html
Enter fullscreen mode Exit fullscreen mode

Simple. Just an index.html. That is all we need.


Step 5: Write the Dockerfile

FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY . /usr/share/nginx/html
EXPOSE 80
Enter fullscreen mode Exit fullscreen mode

Let me break this down:

FROM nginx:alpine — we are using a lightweight version of Nginx as our base image. Alpine Linux is tiny, which keeps our container small and fast.

RUN rm -rf /usr/share/nginx/html/* — wipes out the default Nginx welcome page so our site shows instead.

COPY . /usr/share/nginx/html — copies everything in our current folder (including index.html) into the Nginx web root.

EXPOSE 80 — tells Docker this container will accept traffic on port 80.


Step 6: Build the Image

docker build -t static-site:latest .
Enter fullscreen mode Exit fullscreen mode

Docker pulls nginx:alpine, runs each step in the Dockerfile, and produces an image called static-site:latest. The whole process takes about a minute.

After building, I checked the image sizes:

Image Size
nginx:alpine 93.5 MB
static-site:latest 92.9 MB

Our image is actually slightly smaller than the base because we replaced Nginx's default content with our own lighter files.


Step 7: Run the Container

docker run -d --name static-site \
  -p 80:80 \
  --restart unless-stopped \
  static-site:latest
Enter fullscreen mode Exit fullscreen mode

Breaking down the flags:

  • -d runs the container in the background
  • --name static-site gives it a friendly name
  • -p 80:80 maps port 80 on the VM to port 80 inside the container
  • --restart unless-stopped means it comes back automatically after a VM reboot

Step 8: Verify and Open in Browser

docker ps
Enter fullscreen mode Exit fullscreen mode

Output:

CONTAINER ID   IMAGE                PORTS                    NAMES
83cb16a6cb44   static-site:latest   0.0.0.0:80->80/tcp       static-site
Enter fullscreen mode Exit fullscreen mode

Then I opened http://4.234.163.212 in my browser.

The site loaded.

I cannot fully explain how good that felt. Three months into learning DevOps, and I just served a live website from inside a Docker container running on a cloud VM I spun up myself.


What I Learned

Cloud-init is a game changer. The idea that a VM can configure itself on boot, without any human touching it, is what separates manual setups from real infrastructure automation.

Containers are not complicated. A Dockerfile is just a recipe. Build the recipe, get an image. Run the image, get a container. That is it.

nginx:alpine is perfect for static sites. Small, fast, and it just works.

The --restart flag matters. In production, you want your containers to survive reboots. Always add --restart unless-stopped.


Full Project

GitHub: https://github.com/vivianokose/cloud-vm-docker-deploy


See you in the next one.


Top comments (0)