Docker と最小限の C#（.NET Core）プロジェクトでウェブサーバーを構築する

## 環境

今回使用した環境は以下の通りです。

```bash
$ more /etc/issue
Welcome to Alpine Linux 3.8
Kernel \r on an \m (\l)

$ docker --version
Docker version 18.06.1-ce, build e68fc7a
```

## サンプルの作成

最終的に以下のようなディレクトリ構成になります。

```bash
$ tree
.
├── Dockerfile
├── NetCoreDocker.csproj
└── Program.cs
```

作業ディレクトリを作成し移動します。

```bash
$ mkdir NetCoreDocker && cd NetCoreDocker
```

次に`C#`でウェブサーバーを作成します。
`dotnet new`コマンドや Visual Studio のテンプレートからプロジェクトを作成すると色々とファイルが作られますが、簡易的なエコーサーバーを作るだけなら`.csproj`と`Program.cs`だけでOKです。
`.NET Core 2.1`未満では`Microsoft.AspNetCore.All`という全部入りのメタパッケージを参照する必要がありましたが、`.NET Core 2.1`以降では`Microsoft.AspNetCore.App`だけで足ります。
また[簡易メソッド](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/startup?view=aspnetcore-2.2#convenience-methods)を使用するので`Startup.cs`も不要です。


```NetCoreDocker.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
</Project>
```

```Program.cs
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

class Program {
  static void Main(string[] args) {
    CreateWebHostBuilder(args).Build().Run();
  }

  public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args).Configure(app => {
      app.Run(async (HttpContext c) => {
        await c.Response.WriteAsync("Hello ASP.NET Core!\n");
      });
    });
}
```

次に`Dockerfile`を作成します。
今回は`.csproj`に記述した通り`.NET Core SDK 2.1`を使用します。

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

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

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

## イメージの作成とウェブサーバーの実行

`docker build`コマンドで`Dockerfile`からイメージをビルドします。

```bash
$ docker build -t netcore-docker .
Sending build context to Docker daemon  4.096kB
Step 1/10 : FROM microsoft/dotnet:2.1-sdk AS build-env
2.1-sdk: Pulling from microsoft/dotnet
ab1fc7e4bf91: Pull complete
35fba333ff52: Pull complete
f0cb1fa13079: Pull complete
3d1dd648b5ad: Pull complete
3502bb69183e: Pull complete
5318515718bb: Pull complete
791f48ac5394: Pull complete
Digest: sha256:6f36ceedda6e6f0745b3927569aa52a0ce41c0bb435a826d659c08e4deb50d6d
Status: Downloaded newer image for microsoft/dotnet:2.1-sdk
 ---> b666575243a5
Step 2/10 : WORKDIR /app
 ---> Running in a614d4e023ed
Removing intermediate container a614d4e023ed
 ---> 05baa02c077e
Step 3/10 : COPY *.csproj ./
 ---> 54fe4935b2db
Step 4/10 : RUN dotnet restore
 ---> Running in 8f8dedfa8e50
  Restoring packages for /app/NetCoreDocker.csproj...
  Installing Microsoft.AspNetCore.Razor.Design 2.1.1.
  Generating MSBuild file /app/obj/NetCoreDocker.csproj.nuget.g.props.
  Generating MSBuild file /app/obj/NetCoreDocker.csproj.nuget.g.targets.
  Restore completed in 2.18 sec for /app/NetCoreDocker.csproj.
Removing intermediate container 8f8dedfa8e50
 ---> 446b8c0568ba
Step 5/10 : COPY . ./
 ---> 92e88ccf77ae
Step 6/10 : RUN dotnet publish -c Release -o out
 ---> Running in 4f27fc5a38d6
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 116.16 ms for /app/NetCoreDocker.csproj.
  NetCoreDocker -> /app/bin/Release/netcoreapp2.1/NetCoreDocker.dll
  NetCoreDocker -> /app/out/
Removing intermediate container 4f27fc5a38d6
 ---> b3ba5969eb83
Step 7/10 : FROM microsoft/dotnet:2.1-aspnetcore-runtime
2.1-aspnetcore-runtime: Pulling from microsoft/dotnet
5e6ec7f28fb7: Pull complete
1a8b62bbed68: Pull complete
c7dd7d63c00a: Pull complete
b81e004c72e2: Pull complete
Digest: sha256:17aa602c376cd9586303cf11550048fcc703214eea1a276ee91264a6ea02c07b
Status: Downloaded newer image for microsoft/dotnet:2.1-aspnetcore-runtime
 ---> 796feceedcd5
Step 8/10 : WORKDIR /app
 ---> Running in b25e8ce549a5
Removing intermediate container b25e8ce549a5
 ---> c9cfaafd301d
Step 9/10 : COPY --from=build-env /app/out .
 ---> 4cbe332ba526
Step 10/10 : ENTRYPOINT ["dotnet", "NetCoreDocker.dll"]
 ---> Running in 5330b83bd435
Removing intermediate container 5330b83bd435
 ---> e31e2478535f
Successfully built e31e2478535f
Successfully tagged netcore-docker:latest
```

イメージが作成できたので`myapp`という名前でコンテナを起動します。

```bash
$ docker run -d -p 8080:80 --name myapp netcore-docker
c63ae7ac41f90371fa23f46f1b6263fe7274d28e53582a90b35e1f85451dd334
```

localhost のポート`8080`に`curl`で`GET`リクエストを送ってみると、レスポンスが返ります。

```bash
$ curl http://localhost:8080
Hello ASP.NET Core!
```

## 作成したイメージとコンテナ

`SDK`だけで 1.75GB あります(;・∀・)
マルチステージビルドでランタイムを分けましたがそれでも250MBあります。
今回は Play with Docker のリモート環境で行ったのですが、自宅の遅い回線で作業したら時間がかかりそうですね...

```bash
$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS    NAMES
132bce572c6a        netcore-docker      "dotnet NetCoreDocke…"   5 minutes ago       Up 5 minutes        0.0.0.0:8080>80/tcp   myapp
```

```bash
$ docker images
REPOSITORY          TAG                      IMAGE ID            CREATED             SIZE
netcore-docker      latest                   c633ed1f8228        8 minutes ago       253MB
<none>              <none>                   601313560b09        8 minutes ago       1.75GB
<none>              <none>                   4a1df956ea31        8 minutes ago       253MB
<none>              <none>                   447e743eb681        8 minutes ago       1.75GB
<none>              <none>                   e31e2478535f        11 minutes ago      253MB
<none>              <none>                   b3ba5969eb83        12 minutes ago      1.75GB
microsoft/dotnet    2.1-sdk                  b666575243a5        46 hours ago        1.73GB
microsoft/dotnet    2.1-aspnetcore-runtime   796feceedcd5        46 hours ago        253MB
```