Guest justinyoo Posted August 23 Posted August 23 In my previous post, I discussed different containerising options for .NET developers. Now, let's slightly move the focus to Azure Functions app. How can you containerise the Azure Functions app? Fortunately, Azure Functions Core Tools natively support it with [iCODE]Dockerfile[/iCODE]. But is the [iCODE]Dockerfile[/iCODE] the only option for containerising your .NET-based Azure Functions apps? Throughout this post, I'm going to discuss how Azure Functions apps can be containerised both with and without [iCODE]Dockerfile[/iCODE]. You can find a sample code from this GitHub repository. [HEADING=1]Prerequisites[/HEADING] There are a few prerequisites to containerise .NET-based function apps effectively. .NET SDK 8.0+ Azure Functions Core Tools Visual Studio or Visual Studio Code + C# Dev Kit + Azure Functions Docker Desktop [HEADING=1]Containerise with [iCODE]Dockerfile[/iCODE][/HEADING] With the Azure Functions Core Tools, you can create a new Azure Functions app with the following command: func init FunctionAppWithDockerfile \ --worker-runtime dotnet-isolated \ --docker \ --target-framework net8.0 You may notice the [iCODE]--docker[/iCODE] option in the command. This option creates a [iCODE]Dockerfile[/iCODE] in the project directory. The [iCODE]Dockerfile[/iCODE] looks like this: FROM mcr.microsoft.com/dotnet/sdk:8.0 AS installer-env COPY . /src/dotnet-function-app RUN cd /src/dotnet-function-app && \ mkdir -p /home/site/wwwroot && \ dotnet publish *.csproj --output /home/site/wwwroot # To enable ssh & remote debugging on app service change the base image to the one below # FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-appservice FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0 ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"] There are a few points on [iCODE]Dockerfile[/iCODE] to pick up: The base image for the runtime is [iCODE]mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0[/iCODE]. There are two other options available, [iCODE]mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-slim[/iCODE] and [iCODE]mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-mariner[/iCODE]. You can choose one of them based on your requirements. The function app is running on the [iCODE]/home/site/wwwroot[/iCODE] directory. It has two environment variables, [iCODE]AzureWebJobsScriptRoot[/iCODE] and [iCODE]AzureFunctionsJobHost__Logging__Console__IsEnabled[/iCODE]. There's no [iCODE]ENTRYPOINT[/iCODE] instruction defined. These points will be used later on this post. Let's add a new function to the Azure Functions app with the following command: func new -n HttpExampleTrigger -t HttpTrigger -a anonymous Once it's added, open the [iCODE]HttpExampleTrigger.cs[/iCODE] file and modify it as follows: // Before return new OkObjectResult("Welcome to Azure Functions!"); // After return new OkObjectResult("Welcome to Azure Functions with Dockerfile!"); Now, you can build the container image with the following command: docker build . -t funcapp:latest-dockerfile You have the container image for the Azure Functions app. You can run the container with the following command: docker run -d -p 7071:80 --name funcappdockerfile funcapp:latest-dockerfile Your function app is now up and running in a container. Open the browser and navigate to [iCODE]http://localhost:7071/api/HttpExampleTrigger[/iCODE] to see the function app running. Open your Docker Desktop and check the running container. [ATTACH type=full" alt="Container running Azure Functions app #1]63666[/ATTACH] Can you see the [iCODE]Path[/iCODE] and [iCODE]Args[/iCODE] value? The [iCODE]Path[/iCODE] value is [iCODE]/opt/startup/start_nonappservice.sh[/iCODE] and the [iCODE]Args[/iCODE] value is an empty array. In other words, the shell script, [iCODE]start_nonappservice.sh[/iCODE] is run when the container starts, and it takes no arguments. Keep this information in mind. You'll use it later in this post. Once you're done, stop and remove the container with the following commands: docker stop funcappdockerfile && docker rm funcappdockerfile So far, we've containerised the Azure Functions app with the [iCODE]Dockerfile[/iCODE]. But, as I mentioned before, there is another way to containerise the Azure Functions app without having [iCODE]Dockerfile[/iCODE]. Let's move on. [HEADING=1]Containerise with [iCODE]dotnet publish[/iCODE][/HEADING] Because MSBuild natively supports containerisation, you can make use of the [iCODE]dotnet publish[/iCODE] command to build the container image for your function app. If you want to dynamically set the base container image, this option will be really useful. To do this, you might need to update your [iCODE].csproj[/iCODE] file to include the containerisation settings. First of all, create a new function app without the [iCODE]--docker[/iCODE] option. func init FunctionAppWithMSBuild \ --worker-runtime dotnet-isolated \ --target-framework net8.0 Let's add a new function to the Azure Functions app with the following command: func new -n HttpExampleTrigger -t HttpTrigger -a anonymous Once it's added, open the [iCODE]HttpExampleTrigger.cs[/iCODE] file and modify it as follows: // Before return new OkObjectResult("Welcome to Azure Functions!"); // After return new OkObjectResult("Welcome to Azure Functions with MSBuild!"); Now, the fun part begins. Open the [iCODE]FunctionAppWithMSBuild.csproj[/iCODE] file and add the following node: <ItemGroup> <ContainerEnvironmentVariable Include="AzureWebJobsScriptRoot" Value="/home/site/wwwroot" /> <ContainerEnvironmentVariable Include="AzureFunctionsJobHost__Logging__Console__IsEnabled" Value="true" /> </ItemGroup> Did you find that these two environment variables are the same as the ones in the [iCODE]Dockerfile[/iCODE]? Let's add another node to the [iCODE].csproj[/iCODE] file: <ItemGroup Label="ContainerAppCommand Assignment"> <ContainerAppCommand Include="/opt/startup/start_nonappservice.sh" /> </ItemGroup> This node specifies the command to run when the container starts. This is the same as the [iCODE]Path[/iCODE] value in the [iCODE]Docker Desktop[/iCODE] screenshot. Now, you can build the container image with the following command: dotnet publish ./FunctionAppWithMSBuild \ -t:PublishContainer \ --os linux --arch x64 \ -p:ContainerBaseImage=mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0 \ -p:ContainerRepository=funcapp \ -p:ContainerImageTag=latest-msbuild \ -p:ContainerWorkingDirectory="/home/site/wwwroot" NOTE: You can set the base container image with [iCODE]mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-slim[/iCODE] or [iCODE]mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-mariner[/iCODE], depending on your requirements. This command takes four properties – [iCODE]ContainerBaseImage[/iCODE], [iCODE]ContainerRepository[/iCODE], [iCODE]ContainerImageTag[/iCODE], and [iCODE]ContainerWorkingDirectory[/iCODE]. With this [iCODE]dotnet publish[/iCODE] command, you have the same development experience as building the container image with the [iCODE]docker build[/iCODE] command, without having to rely on [iCODE]Dockerfile[/iCODE]. Run the following command to run the container: docker run -d -p 7071:80 --name funcappmsbuild funcapp:latest-msbuild Your function app is now up and running in a container. Open the browser and navigate to [iCODE]http://localhost:7071/api/HttpExampleTrigger[/iCODE] to see the function app running. Open your Docker Desktop and check the running container. [ATTACH type=full" alt="Container running Azure Functions app #2]63667[/ATTACH] Can you see the [iCODE]Path[/iCODE] and [iCODE]Args[/iCODE] value? The [iCODE]Path[/iCODE] value is [iCODE]/opt/startup/start_nonappservice.sh[/iCODE] and the [iCODE]Args[/iCODE] value has [iCODE]dotnet[/iCODE] and [iCODE]FunctionAppWithMSBuild.dll[/iCODE]. But these arguments are ignored because the shell script doesn't take any argument. Once you're done, stop and remove the container with the following commands: docker stop funcappmsbuild && docker rm funcappmsbuild Now, you've got the Azure Functions app containerised without [iCODE]Dockerfile[/iCODE]. With this [iCODE]dotnet publish[/iCODE] approach, you can easily change the base container image, repository, tag, and working directory without relying on [iCODE]Dockerfile[/iCODE]. So far, I've walked through how .NET developers can containerise their .NET-based Azure Functions apps with two different options. You can either write [iCODE]Dockerfile[/iCODE]s or use [iCODE]dotnet publish[/iCODE] to build container images. Which one do you prefer? [HEADING=1]More about MSBuild for Containers?[/HEADING] If you want to learn more options about containers with MSBuild, the following links might be helpful. .NET Aspire Containerise a .NET app with [iCODE]dotnet publish[/iCODE] .NET container images This article was originally published on Dev Kimchi. Continue reading... Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.