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.
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"]
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
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 thedotnet/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
topath-to-internal.registry.com/dotnet/sdk:5.0-alpine
. - New versions of
dotnet/sdk
anddotnet/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.
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 tocurl
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
.