DEV Community

Cover image for Azure DevOps: build & push docker micro-services to Azure Container Registry
Lester Diaz Perez
Lester Diaz Perez

Posted on

Azure DevOps: build & push docker micro-services to Azure Container Registry

🦆 Prerequisites: Get Your Ducks in a Row

Before we dive in:

Requirement Why You Need It
Azure subscription Need Credits
Docker basics Containerization knowledge
Git workflows Version control fundamentals
GitHub repo Fork this repo
Coffee Optional but highly recommended

Let's Go!


Microservices Architecture:

Image description


🏗️ Step 1: Azure DevOps Foundation

Why Azure DevOps?

  • 🔥 Industry standard for enterprise CI/CD
  • 💎 Free private repos (up to 5 users)
  • 🚀 Seamless Azure integration

Quick Setup:

  1. 🌐 Head to Azure DevOps
  2. 🆕 Create new project
  3. 🔗 Link your forked example-voting-app repo

Image description

🏪 Step 2: Container Registry - Your Image Vault

The Solution: Azure Container Registry (ACR)

  • 🔒 Private & secure
  • Geo-replication
  • 🔗 Native Azure integration

Image description

Think of ACR as: Your private, VIP parking garage for containers!

Quick Create:

Navigate to Container Registry → Create

Image description

Oops! 🤦‍♂️ Need a Resource Group first. Let's fix that...


📁 Step 3: Resource Group - Organization Master

What it is: Think digital folder that keeps all your Azure resources organized.

Why it matters:

  • 💰 Cost tracking per project
  • 🗂️ Easy cleanup (delete group = delete everything)
  • 🎯 Access management in one place

Image description

Naming convention: rg-voting-app-prod (descriptive + environment)


🤖 Step 4: Self-Hosted Agent - Your Build Butler

Hosted vs Self-Hosted: The Truth

Hosted Agents Self-Hosted Agents
💸 Pay per minute 💰 Free after VM cost
🐌 Queue times Instant builds
🔒 Limited customization 🔧 Full control

Let's Go!

Setup Process:

  1. 🖥️ Create Ubuntu VM in Azure
  2. ⚙️ Azure DevOps → Project Settings → Agent Pools → Add pool
  3. 📋 Follow installation steps

Image description

🔐 Security: Personal Access Token (PAT)

Why PAT? Secure communication between agent and Azure DevOps.

Steps:

  1. Profile picture → Personal Access Tokens
  2. Create token with required scopes

Image description

Image description

Security Note: Full access scope = demo only. Production = minimal required permissions! ⚠️


💍 Step 5: Crafting My Preciouuuuss Pipeline

"One pipeline to build them all, one pipeline to find them, one pipeline to bring them all, and in the registry bind them." – DevOps Gandalf

DevOps Sauron

Pipeline Creation Magic ✨

Azure DevOps makes this stupid easy:

  1. Pipelines → New Pipeline
  2. Azure Repos Git
  3. Select repository
  4. Docker - build and push to ACR

!
Image description

Image description
Look at all the infinity of tasks available! Probably all frequently used tasks are already developed here. But you know, if something's missing, you can always create it yourself!

Dockerize and Push Our Vote Microservice

Image description

🐳 Creating the Vote Pipeline

Now let's create a dedicated pipeline for our vote microservice. This pipeline will:

  • Trigger only on vote/ changes (efficient builds)
  • Build the Python Flask app using Docker
  • Push to Azure Container Registry securely
  • Tag with build number for version tracking

Pipeline Configuration:

trigger:
  paths:
    include:
    - vote/*  # Only triggers when vote microservice changes

resources:
- repo: self

variables:
  dockerRegistryServiceConnection: '2833cab0-ae52-474d-97fd-43a2db870ebe'
  imageRepository: 'vote' # For each service one repository (worker, result)
  containerRegistry: 'sharker3312.azurecr.io'
  dockerfilePath: '$(Build.SourcesDirectory)/vote/Dockerfile' # Dockerfile PATH
  tag: '$(Build.BuildId)'

pool:
 name: 'self-host'  # Using our self-hosted agent

stages:
- stage: Build
  displayName: Build and push stage
  jobs:
  - job: Build
    displayName: Build
    steps:
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository)
        dockerfile: $(dockerfilePath)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(tag)
          latest
Enter fullscreen mode Exit fullscreen mode

🚨 REALITY CHECK: When Things Go BOOM! 💥

Because let's be honest - your first pipeline run is going to fail spectacularly, and that's perfectly normal! 😅

🔥 THE MOMENT OF TRUTH 🔥

You hit that "Run Pipeline" button with all the confidence in the world, and then...

Image description

Problem Solved: Our agent needs Docker to build those containers! Let's fix that:

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# Add agent user to docker group (no sudo needed for docker)
sudo usermod -aG docker $USER

Enter fullscreen mode Exit fullscreen mode

Don't worry about errors - they're just DevOps giving you a chance to learn something new! 🧠

🏷️ Tagging Strategy Explained:

Tag Example Purpose
Build ID vote:12345 Specific version tracking
Latest vote:latest Always points to newest build

🔄 Step 7: Worker & Result Microservices

Image description

👷‍♂️ The Worker Pipeline

  imageRepository: 'worker' # Changed from 'vote'
  dockerfilePath: '$(Build.SourcesDirectory)/worker/Dockerfile' # Changed path
Enter fullscreen mode Exit fullscreen mode

Also update your trigger path:

trigger:
  paths:
    include:
    - worker/*  # Only triggers when worker microservice changes
Enter fullscreen mode Exit fullscreen mode

📊 The Result Pipeline

Same story, different microservice! For the Result app, just modify:

  imageRepository: 'result' # Changed from 'vote'
  dockerfilePath: '$(Build.SourcesDirectory)/result/Dockerfile' # Changed path
Enter fullscreen mode Exit fullscreen mode

And update your trigger:

trigger:
  paths:
    include:
    - result/*  # Only triggers when result microservice changes 
Enter fullscreen mode Exit fullscreen mode

Pro tip: 🧙‍♂️ With this approach, each microservice has its own dedicated pipeline that only triggers when that specific service changes! Talk about efficiency!

At this moment we've got all our pipelines functioning beautifully:

Image description

🔄 Step 8: Redis & PostgreSQL - The Missing Pieces

🤔 What about Redis and PostgreSQL?

No need to reinvent the wheel! For these components:

  • 🐘 PostgreSQL - Using official public image from Docker Hub
  • 🔄 Redis - Using official public image from Docker Hub

Why use public images?

  • 🏆 Production-ready containers maintained by experts
  • 🛡️ Security updates automatically rolled out
  • 💰 Cost-efficient - no need to maintain our own images

🚢 What's Next?

But the real question is: What do we do with these container images once they're in our registry?

Well, this is just the beginning of our DevOps journey! Now we need a way to manage these containers through a single orchestration service.

Kubernetes Excitement

Maybe Kubernetes - the container orchestration superhero! 🦸‍♂️

Top comments (0)