Dockerイメージを最適化するのに役立つ重要な5つのプラクティス


## #1 権限は必要最小限に

Dockerfileで指定されない限り、デフォルトだとrootユーザーが使用されます。これは大きなセキュリティリスクに繋がる可能性があるため、アプリケーション専用グループ、専用ユーザーを作成しましょう。

```Dockerfile
# グループを作成
RUN groupadd -r app

# ユーザーを作成
RUN useradd -g app app

# 所有権と権限を設定
RUN chown -R app:app /app
# 再帰的な実行は長い時間がかかることがあるため、パフォーマンスを気にするなら COPY と一緒の方が良い
# COPY --chown=<user>:<group> <hostPath> <containerPath>
COPY --chown=app:app . /app

# ユーザーを切り替え
USER app
```

## #2 マルチステージビルド

Dockerイメージは多くの場合、必要以上に大きくなり、デプロイ、セキュリティ、および開発エクスペリエンスに影響を与えます。また、ツール、開発時のみの依存関係、ランタイム、コンパイラなどの不要なアセットをリリースすることになります。

基本的な考え方は、ビルドステージとランタイムステージを分離することです。

```Dockerfile
FROM golang:1.17.2-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o app

FROM gcr.io/distroless/static AS production
COPY --from=builder app ./
CMD ["/app"]
```

### ちなみにDistrolessイメージとは

今回ランタイムイメージとして使用したDistrolessとは、Googleが公開しているアプリケーションとそのランタイム依存のみが含まれたDebian10(buster)に基づいて作成されたコンテナイメージです。Distrolessにはアプリケーションの実行に必要な依存しか含まれていません。そのため、Shellはおろか、aptやcdといった機能も有していません。要は何も入っていないため、とってもセキュアなランタイム環境としてかつ軽量に使用ができるということです。

https://github.com/GoogleContainerTools/distroless

## #3 セキュリティの脆弱性スキャン

```Dockerfile
docker scan <your-image>
```

`docker scan`でDockerイメージのセキュリティ脆弱性をスキャンできます。CIの一部で使用することをお勧めします。試しに「`node:17-alpine`」と「`node:17`」を `docker scan`してみます。`node:17`では多くの脆弱性が存在していることが明らかです。

#### node:17-alpine

```bash
$ docker scan node:17-alpine

Testing node:17-alpine...

Package manager:   apk
Project name:      docker-image|node
Docker image:      node:17-alpine
Platform:          linux/amd64
Base image:        node:17.5.0-alpine3.15

✔ Tested 16 dependencies for known vulnerabilities, no vulnerable paths found.

According to our scan, you are currently using the most secure version of the selected base image
```

#### node:17

```bash
$ docker scan node:17

Testing node:17...
--- 省略 ---
Package manager:   deb
Project name:      docker-image|node
Docker image:      node:17
Platform:          linux/amd64
Base image:        node:17.5.0-bullseye

Tested 410 dependencies for known vulnerabilities, found 223 vulnerabilities.

Base Image            Vulnerabilities  Severity
node:17.5.0-bullseye  223              8 critical, 16 high, 4 medium, 195 low

Recommendations for base image upgrade:

Alternative image types
Base Image            Vulnerabilities  Severity
node:current-slim     44               0 critical, 0 high, 0 medium, 44 low
node:17-buster-slim   68               0 critical, 1 high, 0 medium, 67 low
node:17-stretch-slim  84               1 critical, 2 high, 0 medium, 81 low
node:buster           411              5 critical, 16 high, 3 medium, 387 low
```

## #4 小さいサイズの公式イメージを使用する

必要なシステムツールとライブラリのみをバンドルし、攻撃対象領域を最小限に抑え、より安全なイメージを確保する、よりスリムなOSディストリビューションでより小さなイメージを使用することをお勧めします。

例えば、[Alpine Linux](https://alpinelinux.org/) はmusllibcとbusyboxに基づくセキュリティ指向の軽量Linuxディストリビューションです。

```bash
FROM node:17        # 【NG】
FROM node:17-alpine # 【OK】
```

## #5 キャッシュを使用する

Dockerイメージにはさまざまなレイヤーが含まれており、Dockerfileでは各コマンドまたは命令がイメージレイヤーを作成します。したがって、Dockerイメージを再構築し、Dockerfileまたはレイヤーが変更されていない場合、Dockerはキャッシュされたレイヤーを使用してイメージを構築します。これにより、イメージの再構築が大幅に高速化されます。

node_modules以下は、 Dockerレイヤーキャッシングを利用してキャッシュする方法の例です。これは、Dockerfileに応じて、複数の状況で実装できます。

```Dockerfile
FROM node:17-alpine

WORKDIR /app

COPY package*.json .

RUN npm install --production

COPY . .

RUN npm run build

CMD ["node", "dist/index.js"]
```

## 参考資料

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/