diff --git a/README.samples.md b/README.samples.md index 7b8c2822b4..bf63c097a7 100644 --- a/README.samples.md +++ b/README.samples.md @@ -61,31 +61,31 @@ See [Hosting ASP.NET Core Images with Docker over HTTPS](https://github.com/dotn Tags | Dockerfile | OS Version -----------| -------------| ------------- dotnetapp-alpine-slim-amd64, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile.alpine-x64-slim) | Alpine -aspnetapp-alpine-slim-amd64, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-x64-slim) | Alpine +aspnetapp-alpine-slim-amd64, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-slim) | Alpine ## Linux arm32 Tags Tags | Dockerfile | OS Version -----------| -------------| ------------- dotnetapp-alpine-slim-arm32v7, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile.alpine-arm32-slim) | Alpine -aspnetapp-alpine-slim-arm32v7, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-arm32-slim) | Alpine +aspnetapp-alpine-slim-arm32v7, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-slim) | Alpine ## Linux arm64 Tags Tags | Dockerfile | OS Version -----------| -------------| ------------- dotnetapp-alpine-slim-arm64v8, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile.alpine-arm64-slim) | Alpine -aspnetapp-alpine-slim-arm64v8, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-arm64-slim) | Alpine +aspnetapp-alpine-slim-arm64v8, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-slim) | Alpine ## Nano Server 2022 amd64 Tags Tag | Dockerfile ---------| --------------- dotnetapp-nanoserver-ltsc2022, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile) -aspnetapp-nanoserver-ltsc2022, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile) +aspnetapp-nanoserver-ltsc2022, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.nanoserver-slim) ## Nano Server, version 1809 amd64 Tags Tag | Dockerfile ---------| --------------- dotnetapp-nanoserver-1809, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile) -aspnetapp-nanoserver-1809, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile) +aspnetapp-nanoserver-1809, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.nanoserver-slim) You can retrieve a list of all available tags for dotnet/samples at https://mcr.microsoft.com/v2/dotnet/samples/tags/list. diff --git a/manifest.samples.json b/manifest.samples.json index 3d2c858e3b..b75707ba4b 100644 --- a/manifest.samples.json +++ b/manifest.samples.json @@ -90,7 +90,7 @@ }, "platforms": [ { - "dockerfile": "samples/aspnetapp/Dockerfile.alpine-x64-slim", + "dockerfile": "samples/aspnetapp/Dockerfile.alpine-slim", "os": "linux", "osVersion": "alpine", "tags": { @@ -108,7 +108,7 @@ }, { "architecture": "arm", - "dockerfile": "samples/aspnetapp/Dockerfile.alpine-arm32-slim", + "dockerfile": "samples/aspnetapp/Dockerfile.alpine-slim", "os": "linux", "osVersion": "alpine", "tags": { @@ -127,7 +127,7 @@ }, { "architecture": "arm64", - "dockerfile": "samples/aspnetapp/Dockerfile.alpine-arm64-slim", + "dockerfile": "samples/aspnetapp/Dockerfile.alpine-slim", "os": "linux", "osVersion": "alpine", "tags": { @@ -145,7 +145,10 @@ ] }, { - "dockerfile": "samples/aspnetapp", + "buildArgs": { + "TAG": "1809" + }, + "dockerfile": "samples/aspnetapp/Dockerfile.nanoserver-slim", "os": "windows", "osVersion": "nanoserver-1809", "tags": { @@ -166,7 +169,10 @@ ] }, { - "dockerfile": "samples/aspnetapp", + "buildArgs": { + "TAG": "ltsc2022" + }, + "dockerfile": "samples/aspnetapp/Dockerfile.nanoserver-slim", "os": "windows", "osVersion": "nanoserver-ltsc2022", "tags": { diff --git a/samples/aspnetapp/Dockerfile.alpine-slim b/samples/aspnetapp/Dockerfile.alpine-slim new file mode 100644 index 0000000000..bc9528fae6 --- /dev/null +++ b/samples/aspnetapp/Dockerfile.alpine-slim @@ -0,0 +1,30 @@ +# https://hub.docker.com/_/microsoft-dotnet +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine AS build +WORKDIR /source + +# copy csproj and restore as distinct layers +COPY aspnetapp/*.csproj . +RUN dotnet restore --use-current-runtime /p:PublishReadyToRun=true + +# copy everything else and build app +COPY aspnetapp/. . +RUN dotnet publish -c Release -o /app --use-current-runtime --no-restore /p:PublishTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true + +# final stage/image +FROM mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine +WORKDIR /app +COPY --from=build /app . + +# This port needs to match the port being used +HEALTHCHECK CMD wget -qO- -t1 http://localhost:80/healthz || exit 1 +ENTRYPOINT ["./aspnetapp"] + +# See: https://github.com/dotnet/announcements/issues/20 +# Uncomment to enable globalization APIs (or delete) +# ENV \ +# DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \ +# LC_ALL=en_US.UTF-8 \ +# LANG=en_US.UTF-8 +# RUN apk add --no-cache \ +# icu-data-full \ +# icu-libs diff --git a/samples/aspnetapp/Dockerfile.nanoserver-slim b/samples/aspnetapp/Dockerfile.nanoserver-slim new file mode 100644 index 0000000000..019930fa6f --- /dev/null +++ b/samples/aspnetapp/Dockerfile.nanoserver-slim @@ -0,0 +1,27 @@ +# escape=` + +ARG TAG=ltsc2022 +# https://hub.docker.com/_/microsoft-dotnet +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +WORKDIR /source + +# copy csproj and restore as distinct layers +COPY aspnetapp/*.csproj . +RUN dotnet restore --use-current-runtime /p:PublishReadyToRun=true + +# copy everything else and build app +COPY aspnetapp/. . +RUN dotnet publish -c Release -o /app --use-current-runtime --no-restore /p:PublishTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true + +# final stage/image +FROM mcr.microsoft.com/windows/nanoserver:$TAG +WORKDIR /app +COPY --from=build /app . +HEALTHCHECK CMD curl -sf --show-error http://localhost:80/healthz || exit 1 +ENV ` + # Configure web servers to bind to port 80 when present + ASPNETCORE_URLS=http://+:80 ` + # Enable detection of running in a container + DOTNET_RUNNING_IN_CONTAINER=true + +ENTRYPOINT ["aspnetapp"] diff --git a/samples/aspnetapp/README.md b/samples/aspnetapp/README.md index e72b0cb0a8..a7d0e1075b 100644 --- a/samples/aspnetapp/README.md +++ b/samples/aspnetapp/README.md @@ -14,9 +14,26 @@ If want to skip ahead, you can try a pre-built version with the following comman docker run --rm -it -p 8000:80 mcr.microsoft.com/dotnet/samples:aspnetapp ``` +You can also call an endpoint that the app exposes: + +```bash +$ curl http://localhost:8000/Environment +{"runtimeVersion":".NET 7.0.2","osVersion":"Linux 5.15.79.1-microsoft-standard-WSL2 #1 SMP Wed Nov 23 01:01:46 UTC 2022","osArchitecture":"X64","user":"root","processorCount":16,"totalAvailableMemoryBytes":67430023168,"memoryLimit":9223372036854771712,"memoryUsage":100577280} +``` + +You can see the app running via `docker ps`. + +```bash +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +d79edc6bfcb6 mcr.microsoft.com/dotnet/samples:aspnetapp "./aspnetapp" 35 seconds ago Up 34 seconds (healthy) 0.0.0.0:8080->80/tcp nice_curran +``` + +You may notice that the sample includes a [health check](https://docs.docker.com/engine/reference/builder/#healthcheck), which is indicated in the "STATUS" column. + ## Build an ASP.NET Core image -You can build and run a .NET-based container image using the following instructions: +You can build and run an image using the following instructions: ```console docker build --pull -t aspnetapp . @@ -33,10 +50,10 @@ Now listening on: http://[::]:80 Application started. Press Ctrl+C to shut down. ``` -After the application starts, navigate to `http://localhost:8000` in your web browser. - > Note: The `-p` argument maps port 8000 on your local machine to port 80 in the container (the form of the port mapping is `host:container`). See the [Docker run reference](https://docs.docker.com/engine/reference/commandline/run/) for more information on command-line parameters. In some cases, you might see an error because the host port you select is already in use. Choose a different port in that case. +After the application starts, navigate to `http://localhost:8000` in your web browser. + You can also view the ASP.NET Core site running in the container on another machine. This is particularly useful if you are wanting to view an application running on an ARM device like a Raspberry Pi on your network. In that scenario, you might view the site at a local IP address such as `http://192.168.1.18:8000`. In production, you will typically start your container with `docker run -d`. This argument starts the container as a service, without any console interaction. You then interact with it through other Docker commands or APIs exposed by the containerized application. @@ -45,35 +62,64 @@ We recommend that you do not use `--rm` in production. It cleans up container re > Note: See [Establishing docker environment](../establishing-docker-environment.md) for more information on correctly configuring Dockerfiles and `docker build` commands. -## Build an image for Windows Nano Server +## Build an image with `HEALTHCHECK` -The following example demonstrates targeting Windows Nano Server (x64) explicitly (you must have [Windows containers enabled](https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers)): +The sample uses [ASP.NET Core Health Check middleware](https://learn.microsoft.com/aspnet/core/host-and-deploy/health-checks). You can direct Docker, Kubernetes, or other systems to use the ASP.NET Core `healthz` endpoint. -```console -docker build --pull -t aspnetapp:nanoserver -f Dockerfile.nanoserver-x64 . -docker run --rm -it -p 8000:80 aspnetapp:nanoserver -``` +The [`HEALTHCHECK`](https://docs.docker.com/engine/reference/builder/#healthcheck) directive is implemented in the [`Dockerfile.alpine-slim`](Dockerfile.alpine-slim) and [`Dockerfile.nanoserver`](Dockerfile.nanoserver-slim). You can build those via the same pattern. -You can view in the app in your browser in the same way as demonstrated earlier. +```bash +$ docker build --pull -t aspnetapp -f Dockerfile.alpine-slim . +$ docker run --rm -it -p 8000:80 aspnetapp +``` -You can use `docker images` to see the images you've built: +In another terminal: -```console -> docker images aspnetapp -REPOSITORY TAG IMAGE ID CREATED SIZE -aspnetapp latest b2f0ecb7bdf9 About an hour ago 353MB -aspnetapp nanoserver d4b7586827f2 About an hour ago 353MB +```bash +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +b143cf4ac0d1 aspnetapp "./aspnetapp" 8 seconds ago Up 7 seconds (health: starting) 0.0.0.0:8000->80/tcp fervent_lichterman ``` -## Build an image for Windows Server Core +After 30s, the status should transition to "healthy" from "health: starting". + +You can also look at health status with `docker inspect`. The following pattern uses `jq`, which makes it much easier to drill in on the interesting data. + +```bash +$ docker inspect b143cf4ac0d1 | jq .[-1].State.Health +{ + "Status": "healthy", + "FailingStreak": 0, + "Log": [ + { + "Start": "2023-01-26T23:39:06.424631566Z", + "End": "2023-01-26T23:39:06.589344994Z", + "ExitCode": 0, + "Output": "Healthy" + }, + { + "Start": "2023-01-26T23:39:36.597795818Z", + "End": "2023-01-26T23:39:36.70857373Z", + "ExitCode": 0, + "Output": "Healthy" + } + ] +} +``` -The instructions for Windows Server Core are very similar to Windows Nano Server. There are three different sample Dockerfile files provided for Windows Server Core, which can all be used with the same approach as the Nano Server ones. +The same thing can be accomplished with PowerShell. -In addition, one of the samples enables using IIS as the Web Server instead of Kestrel. The following example demonstrates using that Dockerfile. +```powershell +> $healthLog = docker inspect 92648775bce8 | ConvertFrom-Json +> $healthLog[0].State.Health.Log -```console -docker build -t aspnetapp -f .\Dockerfile.windowsservercore-iis-x64 . -docker run --rm -it -p:8080:80 aspnetapp +Start End ExitCode Output +----- --- -------- ------ +2023-01-28T10:14:54.589686-08:00 2023-01-28T10:14:54.6137922-08:00 0 Healthy +2023-01-28T10:15:24.6264335-08:00 2023-01-28T10:15:24.6602762-08:00 0 Healthy +2023-01-28T10:15:54.6766598-08:00 2023-01-28T10:15:54.703489-08:00 0 Healthy +2023-01-28T10:16:24.7192354-08:00 2023-01-28T10:16:24.74409-08:00 0 Healthy +2023-01-28T10:16:54.7499988-08:00 2023-01-28T10:16:54.7750448-08:00 0 Healthy ``` ## Build an image for Alpine, Debian or Ubuntu @@ -113,6 +159,37 @@ aspnetapp latest 8c5d1952e3b7 10 hours ago You can run these images in the same way as is done above, with Alpine. +## Build an image for Windows Nano Server + +The following example demonstrates targeting Windows Nano Server (x64) explicitly (you must have [Windows containers enabled](https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers)): + +```console +docker build --pull -t aspnetapp:nanoserver -f Dockerfile.nanoserver-x64 . +docker run --rm -it -p 8000:80 aspnetapp:nanoserver +``` + +You can view in the app in your browser in the same way as demonstrated earlier. + +You can use `docker images` to see the images you've built: + +```console +> docker images aspnetapp +REPOSITORY TAG IMAGE ID CREATED SIZE +aspnetapp latest b2f0ecb7bdf9 About an hour ago 353MB +aspnetapp nanoserver d4b7586827f2 About an hour ago 353MB +``` + +## Build an image for Windows Server Core + +The instructions for Windows Server Core are very similar to Windows Nano Server. There are three different sample Dockerfile files provided for Windows Server Core, which can all be used with the same approach as the Nano Server ones. + +In addition, one of the samples enables using IIS as the Web Server instead of Kestrel. The following example demonstrates using that Dockerfile. + +```console +docker build -t aspnetapp -f .\Dockerfile.windowsservercore-iis-x64 . +docker run --rm -it -p:8080:80 aspnetapp +``` + ## Build an image for ARM32 and ARM64 By default, distro-specific .NET tags target x64, such as `6.0-alpine` or `6.0-focal`. You need to use an architecture-specific tag if you want to target ARM. Note that .NET is only supported on Alpine on ARM64 and x64, and not ARM32. diff --git a/samples/aspnetapp/aspnetapp/Program.cs b/samples/aspnetapp/aspnetapp/Program.cs index c09bfaab3e..43c005f211 100644 --- a/samples/aspnetapp/aspnetapp/Program.cs +++ b/samples/aspnetapp/aspnetapp/Program.cs @@ -2,8 +2,10 @@ // Add services to the container. builder.Services.AddControllersWithViews(); +builder.Services.AddHealthChecks(); var app = builder.Build(); +app.MapHealthChecks("/healthz"); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) @@ -24,9 +26,32 @@ name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); + +CancellationTokenSource cancellation = new(); +app.Lifetime.ApplicationStopping.Register( () => +{ + cancellation.Cancel(); +}); + app.MapGet("/Environment", () => { return new EnvironmentInfo(); }); +// This API demonstrates how to use task cancellation +// to support graceful container shutdown via SIGTERM. +// The method itself is an example and not useful. +app.MapGet("/Delay/{value}", async (int value) => +{ + try + { + await Task.Delay(value, cancellation.Token); + } + catch(TaskCanceledException) + { + } + + return new {Delay = value}; +}); + app.Run();