DEV Community

Kevin Le
Kevin Le

Posted on

Create an Azure Pipelines to deploy Docker image for Azure App Service

This article details how to containerize an app, create an App Service in Azure and configure it to pull the Docker image from the Azure Container Registry (ACR). How the Docker image gets deployed from our code to ACR is a result of us creating a deployment pipeline using Azure Pipelines.

The benefit is instead of manually pushing the locally-built Docker image to ACR after some code changes, we now have an automated Continuous Integration/Continuous Deployment (CI/CD) process.

The workflow can be described as follow: We write the code on our Dev computer, build and run the Docker image locally. Once we do a git push of the code to a git repo, Azure Pipelines performs the build on some agent machine and deploys the Docker image to ACR. Since the App Service has also already been configured for Continuous Deployment, it will pull the image from the ACR automatically. Essentially we have a pipeline from code to repo to Docker image to ACR to App Service.

1) We start with writing an app. What app to write is not the focal point of this article. But to have something to start with, we will create an out-of-the-box NextJS app using npx create-next-app. We could have done with an ASP.Net core app and everything will be just the same. So let's start:

npx create-next-app mysample
cd mysample
npm run build
Enter fullscreen mode Exit fullscreen mode

2) We will create a Docker image for this and run in a local Docker container. To do so, we will create a Dockerfile and a docker-compose.yml file.

Here's the Dockerfile

FROM node:14 as BUILD_IMAGE
WORKDIR /var/www/app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN yarn build

# remove dev dependencies
RUN npm prune --production

FROM node:14
WORKDIR /var/www/app
COPY --from=BUILD_IMAGE /var/www/app/package.json ./package.json
COPY --from=BUILD_IMAGE /var/www/app/node_modules ./node_modules
COPY --from=BUILD_IMAGE /var/www/app/.next ./.next
COPY --from=BUILD_IMAGE /var/www/app/public ./public

EXPOSE 3000
CMD ["yarn", "start"]
Enter fullscreen mode Exit fullscreen mode

And docker-compose.yml file

version: '3.4'

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - 80:3000
    env_file: .env
    command:
      sh -c 'yarn start'
Enter fullscreen mode Exit fullscreen mode

3) Now we can run the Docker locally with the command

docker-compose up --build

It should work if you point the browser to http://localhost (that's port 80 not 3000)

4) All the next steps that we're about to do can be done with the UI in Azure Portal, but we will use Azure CLI as much as possible. Also, I will name my Resource Group and App Service Plan as MyResourceGroup and MyLinuxPlan respectively. If you already have a Resource Group and App Service plan, you can continue using them. Or you can follow next step and substitute with names of your own choosing.

Let's create a Resource Group called MyResourceGroup

az group create -l westus -n MyResourceGroup
Enter fullscreen mode Exit fullscreen mode

Now we create a Linux App Service Plan called MyLinuxPlan with 2 workers

az appservice plan create -g MyResourceGroup -n MyLinuxPlan \
    --is-linux --number-of-workers 2 --sku S1
Enter fullscreen mode Exit fullscreen mode

To create a Resource Group, refer to this link.
To create an App Service Plan refer to this link

5) Now we create an Azure Container Registry called mysampleacr

az acr create --name mysampleacr --resource-group \
    MyResourceGroup --sku Basic --admin-enabled true
Enter fullscreen mode Exit fullscreen mode

At this point, we now have an ACR but no image has been pushed to it and no App Service has been created. This is where my approach starts to differ from that in Azure documentation.

This is OK because we don't want to push the Docker image to ACR manually. Instead we will set up the Azure Pipeline now.

6) Let's go to https://dev.azure.com, navigate to the Pipeline page and create a new Pipeline as shown in the next picture:
Azure Pipeline

7) Then specify Where is you code? when prompted.

8) At the page Configure your pipeline, scroll down and click on "Show more". Then select Docker Build and push an image to Azure Container Registry

9) Select your Subscription and Continue. You might be prompted to login again.

10) Now select your Container Registry. If you follow this example, earlier we name it mysampleacr. For image name, let's name it mysampleimg. Then click on Validate and configure

11) We should get a generated azure-pipelines.yml file with contents similar to the one below

# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
- main

resources:
- repo: self

variables:
  # Container registry service connection established during pipeline creation
  dockerRegistryServiceConnection: 'b0...a4'
  imageRepository: 'mysampleimg'
  containerRegistry: 'mysampleacr.azurecr.io'
  dockerfilePath: '**/Dockerfile'
  tag: '$(Build.BuildId)'

  # Agent VM image name
  vmImageName: 'ubuntu-latest'

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

We need to make a couple of changes and then we can Save and run.

In the above file, look for the line tag: '$(Build.BuildId)' and add a line below it. It will now show

tag: '$(Build.BuildId)'
latestTag: 'latest'
Enter fullscreen mode Exit fullscreen mode

In the last 2 lines of the file, change from

tags: |
  $(tag)
Enter fullscreen mode Exit fullscreen mode

to

tags: |
  $(latestTag)
Enter fullscreen mode Exit fullscreen mode

The resulting/modified azure-pipelines.yml file should now look something like

# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
- main

resources:
- repo: self

variables:
  # Container registry service connection established during pipeline creation
  dockerRegistryServiceConnection: 'b0...a4'
  imageRepository: 'mysampleimg'
  containerRegistry: 'mysampleacr.azurecr.io'
  dockerfilePath: '**/Dockerfile'
  tag: '$(Build.BuildId)'
  latestTag: 'latest'

  # Agent VM image name
  vmImageName: 'ubuntu-latest'

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

Now we can Save and run

12) When the run finishes, we can verify there is an image in the ACR by running the following commands (one by one)

az acr repository list -n mysampleacr

az acr repository show -n mysampleacr \
  --repository mysampleimg

az acr repository show -n mysampleacr  \
  --repository mysampleimg:latest
Enter fullscreen mode Exit fullscreen mode

For more help on az acr repository commands, please refer to https://docs.microsoft.com/en-us/cli/azure/acr/repository?view=azure-cli-latest#az_acr_repository_list

13) Now we can create an App Service and tell it to pull from the latest image mysampleimg:latest from the ACR mysampleacr.

az webapp create --resource-group MyResourceGroup --plan MyLinuxPlan --name mysample --deployment-container-image-name mysampleacr.azurecr.io/mysampleimg:latest
Enter fullscreen mode Exit fullscreen mode

14) To ensure when a new image is pushed to the ACR, the App Service will automatically pull the latest image, go to Azure Portal, navigate to the App Service blade and make sure Continuous Deployment is on.
App Service Continuous Deployment

References (A little difficult to read for me, but that's just my opinion):
https://docs.microsoft.com/en-us/azure/app-service/quickstart-nodejs?pivots=platform-linux

https://docs.microsoft.com/en-us/azure/architecture/example-scenario/apps/devops-dotnet-webapp

https://docs.microsoft.com/en-us/azure/developer/javascript/tutorial/deploy-nodejs-azure-app-service-with-visual-studio-code?tabs=bash

https://docs.microsoft.com/en-us/azure/app-service/quickstart-custom-container?tabs=node&pivots=container-linux

Discussion (0)