AdvancedLesson 9 of 16

🚀 Docker in CI/CD with Azure DevOps and GitHub Actions

Automate the full image lifecycle — build, test, scan, tag, and push to ACR — using Azure DevOps Pipelines and GitHub Actions workflows.

🧒 Simple Explanation (ELI5)

Without CI/CD, every developer builds and pushes their own images manually — dangerous and inconsistent. With CI/CD, every code push automatically triggers a robot that builds a fresh, tested, and properly tagged image and puts it in the registry, ready to deploy. You never run docker build in production manually.

💡
Golden rule for CI/CD with Docker

Build once, promote everywhere. The same image built in CI is promoted through dev → staging → prod. Never rebuild between environments — rebuilding introduces the risk of non-deterministic differences.

🔧 GitHub Actions — Full Docker Workflow

yaml
# .github/workflows/docker.yml
name: Build and Push Docker Image

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: myacr.azurecr.io
  IMAGE_NAME: myapp

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Log in to Azure Container Registry
        uses: azure/docker-login@v1
        with:
          login-server: ${{ env.REGISTRY }}
          username: ${{ secrets.ACR_USERNAME }}
          password: ${{ secrets.ACR_PASSWORD }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=git-
            type=semver,pattern={{version}}
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

🔧 Azure DevOps Pipeline

yaml
# azure-pipelines.yml
trigger:
  branches:
    include: [main]

variables:
  acrLoginServer: myacr.azurecr.io
  imageName: myapp
  tag: $(Build.BuildId)

pool:
  vmImage: ubuntu-latest

stages:
- stage: Build
  jobs:
  - job: BuildAndPush
    steps:
    - task: AzureCLI@2
      displayName: Login to ACR
      inputs:
        azureSubscription: MyAzureConnection
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: az acr login --name myacr

    - task: Docker@2
      displayName: Build and Push
      inputs:
        command: buildAndPush
        repository: $(imageName)
        containerRegistry: MyACRServiceConnection
        tags: |
          $(tag)
          latest

🐛 Debugging Scenario

Problem: CI builds succeed but the pushed image is 800MB — too large, slow to pull.

bash
# Step 1: analyze the image layers locally docker image history myapp:latest --no-trunc | head -20 # Step 2: use dive tool for visual layer analysis docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \ wagoodman/dive myapp:latest # Common causes of bloat: # - node_modules or build tools left in final image -> use multi-stage build # - COPY . . before .dockerignore is properly set up # - Multiple RUN commands not chained (each creates a layer) # - Debug packages installed but not removed # Fix: switch to multi-stage build (covered in Lesson 11)

🎯 Interview Questions

What is the recommended tagging strategy for CI/CD pipelines?▾

Tag with multiple labels simultaneously: 1) git SHA (git-a1b2c3d) for immutable traceability. 2) Semantic version (1.2.3) when a release is cut. 3) Environment tag (staging, prod) for promotion tracking. Never use only :latest — it is not traceable. The docker/metadata-action in GitHub Actions automates this correctly.

How do you securely authenticate to ACR in a CI/CD pipeline?▾

Best practice for Azure: use a Managed Identity (for Azure-hosted runners) or a service principal with minimal ACR push permissions (AcrPush role). Store credentials as pipeline secrets — never hardcode them. In GitHub Actions use GITHUB_SECRETS; in Azure DevOps use variable groups with key vault integration. Rotate service principal credentials regularly.

Scenario: Your CI spends 8 minutes building the Docker image on every run. How do you speed it up?▾

1. Order Dockerfile layers correctly (deps before code) for cache hits. 2. Use GitHub Actions cache for layer storage: cache-from/to: type=gha. 3. Use Docker BuildKit for parallel execution. 4. Use registry cache: build with --cache-from registry pulling previous layers. 5. Use multi-stage builds to avoid unnecessary work. 6. Pin base image versions to avoid unnecessary re-pulls.

📋 Summary