F# ASP.NET Core Dockerfile

The base Dockerfile I use for ASP.NET Core web applications. This is suitable for both F# and C# apps. Below I detail some design decisions and Docker best practices.

Link to this section Dockerfile

FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS build

WORKDIR /app

# Copy source code and compile
COPY ./ ./
RUN dotnet restore

RUN dotnet publish --configuration Release -o bin

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine AS runtime

LABEL com.org.author=foo
LABEL com.org.component=bar-api

WORKDIR /app
COPY --from=build /app/bin .

HEALTHCHECK --interval=5s --timeout=10s --retries=1 CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1

ENTRYPOINT ["dotnet", "bar-api.App.dll"]

Link to this section Quick Start

Build the testing image

docker build -t bar-api .

Run it

docker run -it --rm -p 5000:80 --name local-bar-api-test bar-api

Link to this section Docker Base Images

Use dotnet/sdk:5.0-alpine and dotnet/aspnet:5.0-alpine for fast, secure, and reproducible images. Some key considerations:

  • dotnet/ images are published by Microsoft. We trust they know what they’re doing.
  • The application is compiled within the dotnet/sdk image. The resulting binaries are copied to the dotnet/aspnet image which has the minimal set of libraries required to serve the application.
  • Alpine Linux gives us security and a small resulting Docker image. Docker image size is important for storage costs as well as speed of execution of our deployment pipelines.
  • If your application is being developed within an organisation with a private Docker Registry then use it. ie replace mcr.microsoft.com/dotnet/sdk:5.0-alpine to path-to-internal.registry.com/dotnet/sdk:5.0-alpine.
  • New versions of dotnet/sdk and dotnet/aspnet are published whenever the underlying Alpine or .NET 5 framework version changes. I find this is a good balance of security and reproducibility but if you need absolute reproducibility of the docker images then consider pinning to a specific version.

Link to this section Docker Healthcheck

A Docker HEALTHCHECK provides a basic indicator to the container orchestrator about the status of the container. Container orchestrators provide different capabilities; Kubernetes offers Liveness, Readiness, and Startup checks for you to configure independent of the Dockerfile. This allows you to differentiate between failure modes of:

  • The container failed to start due to some static reason such as a typo in the configuration. Restarting the container won’t fix the problem.
  • The container has a memory leak and has now stopped serving requests. If the orchestrator restarts the container the problem will be fixed.
  • A critical downstream dependency of the container is down. Don’t route it traffic until the dependency comes back up.

Kubernetes is complex and the orchestrator you use may not have those features. Whether you use Kubernetes or not, keep it simple and configure the Dockerfile HEALTHCHECK to be able to check that the ASP.NET Core web server is running without error. Add a /ping endpoint to your REST API and hard code it to return an HTTP 200. When the container starts Docker (or the orchestrator) runs the HEALTHCHECK and sets the container status to Healthy once it confirms the endpoint is accessible.

HEALTHCHECK --interval=5s --timeout=10s --retries=1 CMD wget --no-verbose --tries=1 --spider http://localhost/ping || exit 1
  • wget is preferred to curl as it comes packaged with Alpine Linux.
  • Although we expect a GET /ping to return nearly instantly be aware of the startup time of the ASP.NET Core server. Factor this in to the --timeout and --retries.

Related Posts