﻿# 如何將 ASP.NET Core 2.1 網站部署到 Docker 容器中

從 .NET Core 2.1 開始，無論是 **.NET Core** 或 **ASP.NET Core** 專案，容器映像全部統一由 Docker Hub 上面的 [microsoft/dotnet](https://hub.docker.com/r/microsoft/dotnet/) 專案提供。今天這篇文章，我就來說明如何將一份已經寫好的 ASP.NET Core 2.1 網站專案，透過 Docker 部署到容器中。

### 取得最新穩定版 Tag 名稱

你在建立容器映像時候，可以記得以下三個 Tag 名稱，這三個 Tag 會永遠指向最新版的 .NET Core 版本，分別是：

-   [2.1-sdk](https://github.com/dotnet/dotnet-docker/blob/949b31f5c80de736b0ef33ccfd15cb087289883e/2.1/sdk/stretch/amd64/Dockerfile)
    -   內建完整的 .NET Core 2.1 SDK 環境，可以幫助你建置 .NET Core 應用程式。
-   [2.1-runtime](https://github.com/dotnet/dotnet-docker/blob/949b31f5c80de736b0ef33ccfd15cb087289883e/2.1/runtime/stretch-slim/amd64/Dockerfile)
    -   內建 .NET Core 2.1 Runtime 執行環境。(不含 SDK 工具)
    -   內建 Microsoft.NETCore.App 中繼套件與所有相依 NuGet 套件。
-   [2.1-aspnetcore-runtime](https://github.com/dotnet/dotnet-docker/blob/41ac0ca18a1d0a778af57cc1ffdb9290e761d182/2.1/aspnetcore-runtime/stretch-slim/amd64/Dockerfile)
    -   內建 .NET Core 2.1 Runtime 執行環境。(不含 SDK 工具)
    -   內建 Microsoft.NETCore.App 中繼套件與所有相依 NuGet 套件。
    -   內建 Microsoft.AspNetCore.App 與 Microsoft.AspNetCore.All 中繼套件與所有相依 NuGet 套件。

上述三個 Tag 名稱會連結到各自的 Dockerfile 檔案，建議大家可以先行讀懂內容。

想了解 .NET Core 如何部署到容器中，參考 [.NET Core Docker Samples](https://github.com/dotnet/dotnet-docker/blob/master/samples/README.md) 算是一個不錯的管道，因為有相當完整的範例可參考。

**請注意**：如果你已經下載過這三個 Tag 的容器映像(Docker Image)，預設在執行 `docker build` 的過程，並不會自動到 Docker Hub 下載最新版本。如果你要更新到最近的穩定版，必須透過 `docker pull` 重新下載才行！

### 認識 ASP.NET Core 的 Docker 容器範例

我們可以從 [ASP.NET Core Docker Sample](https://github.com/dotnet/dotnet-docker/blob/main/samples/AspNetCoreRazorApp/README.md) 文件中，得知許多建置 Docker 容器映像的範例。我們就從該文章的第一個簡易範例來看，其重點如下：

1.  該 Dockerfile 範例使用了 [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) 建置技巧，並分成兩個步驟執行：
    1.  先透過 `microsoft/dotnet:2.1-sdk` 建置 ASP.NET Core 應用程式
    2.  再透過 `microsoft/dotnet:2.1-aspnetcore-runtime` 將建置好的 ASP.NET Core 應用程式，封裝進 ASP.NET Core 執行環境容器中。
2.  在第一個 stage build 中，又分成兩個步驟：
    1.  先取得 `*.sln` 與 `*.csproj` 檔案，並還原專案中會用到的 NuGet 套件。
    2.  複製完整的 ASP.NET Core 原始碼到目錄中，並執行 `dotnet publish` 發布應用程式到 `out` 資料夾下。
3.  在第二個 stage build 中，複製第一個 stage build 的完整發布結果到新的容器中，然後設定 `ENTRYPOINT` 指定容器的啟動命令。

```dockerfile
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /app

# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore

# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /app/aspnetapp
RUN dotnet publish -c Release -o out

FROM microsoft/dotnet:2.1-aspnetcore-runtime AS runtime
WORKDIR /app
COPY --from=build /app/aspnetapp/out ./
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
```

完整的實作練習流程如下：

1.  取得範例原始碼
    
    ```bash
    git clone https://github.com/dotnet/dotnet-docker.git
    cd dotnet-docker/samples/aspnetapp
    ```
    
2.  建置 aspnetapp 容器映像
    
    ```bash
    docker build -t aspnetapp .
    ```
    
3.  執行自訂的 aspnetapp 容器
    
    ```bash
    docker run -d --name=myapp --rm -p 8000:80 aspnetapp
    ```
    
    連到 `http://localhost:8000/` 就可以看到網頁執行。
    
4.  停止容器
    
    ```bash
    docker stop myapp
    ```
    

這份 ASP.NET Core 容器範例最有趣的地方是，他可以讓你在本機不需要安裝任何 .NET Core SDK 環境，透過預先製作好的 Docker Image 就可以讓你快速的建置與發行專案。這樣的設計，最能展現效益的地方，就是 CI/CD 的過程會非常簡單，完全沒有環境的相依性！

### 進一步認識 `microsoft/dotnet:2.1-aspnetcore-runtime` 容器映像

如果深入查看這份 [microsoft/dotnet:2.1-aspnetcore-runtime](https://github.com/dotnet/dotnet-docker/blob/41ac0ca18a1d0a778af57cc1ffdb9290e761d182/2.1/aspnetcore-runtime/stretch-slim/amd64/Dockerfile) 容器映像，你會發現他的 Base Image 是 [microsoft/dotnet:2.1-runtime-deps-stretch-slim](https://github.com/dotnet/dotnet-docker/blob/949b31f5c80de736b0ef33ccfd15cb087289883e/2.1/runtime-deps/stretch-slim/amd64/Dockerfile)，其內容如下：

```dockerfile
FROM debian:stretch-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        ca-certificates \
        \
# .NET Core dependencies
        libc6 \
        libgcc1 \
        libgssapi-krb5-2 \
        libicu57 \
        liblttng-ust0 \
        libssl1.0.2 \
        libstdc++6 \
        zlib1g \
    && rm -rf /var/lib/apt/lists/*

# Configure Kestrel web server to bind to port 80 when present
ENV ASPNETCORE_URLS=http://+:80 \
    # Enable detection of running in a container
    DOTNET_RUNNING_IN_CONTAINER=true
```

最後兩行，定義了兩個環境變數，尤其是 `ASPNETCORE_URLS` 最為重要，這個環境變數決定了你的 ASP.NET Core 應用程式要對外 Listen 哪一個 IP:Ports，這裡的 `+` 代表的是 Binding 所有的本機 IP 位址，而 80 就代表了只 Listen 埠號 80 (這裡是指容器內的 Port 80)。也因此，我們在執行這個容器時，需要特別加上 `-p 8000:80` 的關係，這代表著我們容器主機的 8000 Port 會對應到容器中的 80 Port 的意思。

```bash
docker run -d -p 8000:80 aspnetapp
```

### 如何建立含有 HTTPS 加密連線的 ASP.NET Core 網站容器

我們在建立容器映像的時候，我們都會大量利用「環境變數」來調整容器執行時期的運作邏輯，例如指定 IP:Port，或是設定 TLS/SSL 憑證金鑰等等。如果你想了解如何部署含有 HTTPS 加密連線的 ASP.NET Core 2.1 網站在 Docker 容器中，可以參考 [Hosting ASP.NET Core Images with Docker over HTTPS](https://github.com/dotnet/dotnet-docker/blob/main/samples/host-aspnetcore-https.md) 這份文件下去設定。

如果你在 Windows 環境下使用 Linux 容器，可以參考以下步驟進行測試：

1.  先建立開發測試用的自簽憑證
    
    ```bash
    dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p crypticpwd
    dotnet dev-certs https --trust
    ```
    
2.  再透過環境變數指定相關憑證與相關參數
    
    你只要透過指定「環境變數」就可以順利將先前建立建立好的 `aspnetapp` 容器映像，自動跑成含有 HTTPS 加密連線的 Kestrel 伺服器。
    
    ```bash
    docker run --rm -it -p 8000:80 -p 8001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=8001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="crypticpwd" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v %USERPROFILE%\.aspnet\https:/https/ aspnetapp
    ```
    
    連到 `https://localhost:8001/` 就可以看到 HTTPS 加密網頁執行。
    

**相關連結**

-   [官方 .NET Docker 映像 | Microsoft Docs](https://learn.microsoft.com/zh-tw/dotnet/standard/microservices-architecture/net-core-net-framework-containers/official-net-docker-images)
-   [microsoft/dotnet - Docker Hub](https://hub.docker.com/r/microsoft/dotnet/)
-   [.NET Core Docker Samples](https://github.com/dotnet/dotnet-docker/blob/master/samples/README.md)
-   [ASP.NET Core Docker Sample](https://github.com/dotnet/dotnet-docker/blob/main/samples/AspNetCoreRazorApp/README.md)
-   [Develop ASP.NET Core Applications in a Container](https://github.com/dotnet/dotnet-docker/blob/cb7a9c35dacf6d34fcf7bab7995e60faef55f61f/samples/aspnetapp/aspnet-docker-dev-in-container.md)
-   [HTTP 301 Moved Permanently](https://github.com/dotnet/dotnet-docker/tree/949b31f5c80de736b0ef33ccfd15cb087289883e/2.1)
