Posts Deploy a Docker Container to Azure Functions using an Azure DevOps YAML Pipeline
Deploy a Docker Container to Azure Functions using an Azure DevOps YAML Pipeline
Cancel

Deploy a Docker Container to Azure Functions using an Azure DevOps YAML Pipeline

In my last post, I created a YAML pipeline to build and deploy an Azure Function to Azure. Today, I will build the same Azure Function inside a Docker container and deploy the container to Azure.

Add Docker to the Azure Function

You can find the code of the demo on Github.

To add Docker to the Azure Function, right-click the project in Visual Studio and click Add –> Docker Support. Visual Studio automatically creates the Dockerfile for you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM mcr.microsoft.com/azure-functions/dotnet:3.0 AS base
WORKDIR /home/site/wwwroot
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
WORKDIR /src
COPY ["OrderApi.Messaging.Receive/OrderApi.Messaging.Receive.csproj", "OrderApi.Messaging.Receive/"]
RUN dotnet restore "OrderApi.Messaging.Receive/OrderApi.Messaging.Receive.csproj"
COPY . .
WORKDIR "/src/OrderApi.Messaging.Receive"
RUN dotnet build "OrderApi.Messaging.Receive.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "OrderApi.Messaging.Receive.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /home/site/wwwroot
COPY --from=publish /app/publish .
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

If you want to learn more about adding Docker to a .NET 5 project, see my post Dockerize an ASP .NET Core Microservice and RabbitMQ.

Build a Docker Container inside a YAML Pipeline in Azure DevOps

In Azure DevOps, create a new pipeline (or edit the one form last post) and add the following variables:

1
2
3
4
5
6
7
8
variables:
  ArtifactName: 'OrderApi.Messaging.Receive'  
  ConnectionString: "Server=tcp:$(SQLServerName),1433;Initial Catalog=$(DatabaseName);Persist Security Info=False;User ID=$(DbUser);Password=$(DbPassword);MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
  ContainerRegistry: 'Docker Hub'
  DatabaseName: Order  
  SQLServerName: wolfgangmicroservicedemosql.database.windows.net # replace with your server url
  Repository: 'wolfgangofner/orderapimessagingreceive'
  Tag: $(GitVersion.NuGetVersionV2)

Additionally, add DbUser, DbPassword, and QueueConnectionString as secret variables to the pipeline. These variables contain the database user and password and the connection string to the Azure Service Bus Queue. If you want to deploy your own Azure Function without a connection to other services, then you obviously don’t have to add the variables.

Next, add a job inside a stage and create a build version using GitVersion. If you want to learn more about versioning, see my post Automatically Version Docker Containers in Azure DevOps CI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stages:
- stage: Build_and_Publish
  displayName: Build and Publish container
  jobs:
  - job: 'Build_and_Publish'
    displayName: Build and publish the container
    steps:
    - task: gitversion/setup@0
      displayName: Install GitVersion
      inputs:
        versionSpec: '5.5.0'
        
    - task: gitversion/execute@0
      displayName: Determine Version

With the version number in place, add two more tasks to build the Docker container and then push it to a registry. In this demo, I am pushing it to DockerHub.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- task: Docker@2  
  inputs:
    containerRegistry: $(ContainerRegistry)
    repository: $(Repository)
    command: 'build'
    Dockerfile: '**/$(ArtifactName)/$(ArtifactName)/Dockerfile'
    buildContext: 'AzureFunctions/$(ArtifactName)'
    tags: |      
      $(Tag)
      latest   
  displayName: 'Build Docker Container'

- task: Docker@2  
  inputs:
    containerRegistry: $(ContainerRegistry)
    repository: $(Repository)
    command: 'push'
    tags: |      
      $(Tag)
      latest
  condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
  displayName: 'Push Docker Container'

The last step is to deploy the previously created Docker container to the Azure Function and then pass the database and queue connection string to its settings. For more information about deploying an Azure Function with Azure DevOps see my last post, Deploy Azure Functions with Azure DevOps YAML Pipelines. Note that the Azure Function must exist, otherwise the deployment will fail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- task: AzureFunctionAppContainer@1 
  inputs:
    azureSubscription: 'AzureServiceConnection'
    appName: 'microservicedemoOrderApiMessagingReceive'
    imageName: '$(Repository):$(Tag)'

- task: AzureAppServiceSettings@1
  inputs:
    azureSubscription: 'AzureServiceConnection'
    appName: 'microservicedemoOrderApiMessagingReceive'
    resourceGroupName: 'MicroserviceDemo'
    appSettings: |
      [
        {
          "name": "QueueConnectionString",
          "value": "$(QueueConnectionString)",
          "slotSetting": false
        },
        {
          "name": "DatabaseConnectionString",
          "value": "$(ConnectionString)", 
          "slotSetting": false
        }
      ]
  displayName: Update App Settings
  condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))

The full pipeline looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
name : OrderApi.Messaging.Receive-Docker-CI-azure-pipeline.yml
trigger:
  branches:
    include:
      - master
  paths:
    include:
      - AzureFunctions/OrderApi.Messaging.Receive/*

pool:
  vmImage: 'ubuntu-latest'

variables:
  ArtifactName: 'OrderApi.Messaging.Receive'  
  ConnectionString: "Server=tcp:$(SQLServerName),1433;Initial Catalog=$(DatabaseName);Persist Security Info=False;User ID=$(DbUser);Password=$(DbPassword);MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
  ContainerRegistry: 'Docker Hub'
  DatabaseName: Order  
  SQLServerName: wolfgangmicroservicedemosql.database.windows.net # replace with your server url
  Repository: 'wolfgangofner/orderapimessagingreceive'
  Tag: $(GitVersion.NuGetVersionV2)

stages:
- stage: Build_and_Publish
  displayName: Build and Publish container
  jobs:
  - job: 'Build_and_Publish'
    displayName: Build and publish the container
    steps:
    - task: gitversion/setup@0
      displayName: Install GitVersion
      inputs:
        versionSpec: '5.5.0'
        
    - task: gitversion/execute@0
      displayName: Determine Version

    - task: Docker@2
      displayName: 'Build Docker Container'
      inputs:
        containerRegistry: $(ContainerRegistry)
        repository: $(Repository)
        command: 'build'
        Dockerfile: '**/$(ArtifactName)/$(ArtifactName)/Dockerfile'
        buildContext: 'AzureFunctions/$(ArtifactName)'
        tags: |      
          $(Tag)
          latest        
  
    - task: Docker@2
      displayName: 'Push Docker Container'
      inputs:
        containerRegistry: $(ContainerRegistry)
        repository: $(Repository)
        command: 'push'
        tags: |      
          $(Tag)
          latest
      condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))

    - task: AzureFunctionAppContainer@1 
      inputs:
        azureSubscription: 'AzureServiceConnection'
        appName: 'microservicedemoOrderApiMessagingReceive'
        imageName: '$(Repository):$(Tag)'

    - task: AzureAppServiceSettings@1
      inputs:
        azureSubscription: 'AzureServiceConnection'
        appName: 'microservicedemoOrderApiMessagingReceive'
        resourceGroupName: 'MicroserviceDemo'
        appSettings: |
          [
            {
              "name": "QueueConnectionString",
              "value": "$(QueueConnectionString)",
              "slotSetting": false
            },
            {
              "name": "DatabaseConnectionString",
              "value": "$(ConnectionString)", 
              "slotSetting": false
            }
          ]
      displayName: Update App Settings
      condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))

Save the pipeline and run it.

The pipeline ran sucessfully

The pipeline ran sucessfully

You can see the Docker container with its two tags on DockerHub after the build is finished.

The Azure Function Container got pushed to DockerHub

The Azure Function Container got pushed to DockerHub

Testing the Azure Function is exactly the same as in my last post, Deploy Azure Functions with Azure DevOps YAML Pipelines.

Conclusion

This short post showed how to create a Docker container of an Azure Function inside an Azure DevOps YAML pipeline. The Docker image was published to DockerHub and then deployed to an existing Azure Function.

You can find the code of the demo on Github.

This post is part of “Microservice Series - From Zero to Hero”.

This post is licensed under CC BY 4.0 by the author.