Docker に Composer をインストールするベストプラクティス（と解説）

Docker に Composer をインストールする際，Composer のバージョンアップのたびにハッシュ値を書き換えるのが面倒だったので，何か解決策はないかと調べていたところ[こちら](https://blog.hanhans.net/2019/01/08/docker-composer/)の記事を発見しました。
正直これだけでいいじゃんという話ですが，内容が大変ミニマルでしたので，解説を交えて記事にさせていただきました。


## 結論
Dockerfile に以下を追記するだけで OK 。

```Dockerfile
COPY --from=composer /usr/bin/composer /usr/bin/composer
```

ただし [multi-stage builds (マルチステージビルド)](https://docs.docker.com/develop/develop-images/multistage-build/) を使用するため，Docker のバージョンは `>=17.05` が必須です。

以下で解説します。


## メジャーな方法（Composer 公式）
[Composer 公式](https://getcomposer.org/download/) のインストール方法を Dockerfile に記述するのが一般的かと思います。

例：PHP-FPM に Composer をインストールしたイメージを作る Dockerfile

```Dockerfile
FROM php:7.3-fpm

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer
```

### この方法の問題点
上記の方法の4行目内のハッシュ値が問題です。

```bash
php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
```

このハッシュ値は Composer はバージョンアップごとに変更されるので（上記は2019/09/30現在），そのたびに Dockerfile も変更しなければなりません。

**めんどうです。**


## 解決法
[Docker 公式の Composer イメージ](https://hub.docker.com/_/composer) を使用します。
Docker 17.05 から導入されたマルチステージビルドを使えば非常にシンプルに記述できます。
（すべて Docker 公式の Composer イメージのページの DESCRIPTION に記載されています）

> - (optimal) create your own build image and install Composer inside it.<br>
**Note**: Docker 17.05 introduced multi-stage builds, simplifying this enormously:

> ```Dockerfile
COPY --from=composer /usr/bin/composer /usr/bin/composer
```

以下の `:1.8.6` のようにタグでバージョン指定もできますし，常に最新がよければタグなしもしくは `:latest` を付ければ OK です。

```Dockerfile
COPY --from=composer:1.8.6 /usr/bin/composer /usr/bin/composer
```

### マルチステージビルドとは？

[公式ドキュメント](https://docs.docker.com/develop/develop-images/multistage-build/)

> With multi-stage builds, you use multiple `FROM` statements in your Dockerfile. Each `FROM` instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image. 

簡単にいうと，ひとつの Dockerfile で複数のビルドができ，各ビルドの段階で必要な部分だけコピーして（残りは破棄される），最終的に使用したいベースイメージにペーストできる機能です。

例：golang をビルドしたバイナリだけを Alpine Linux イメージにコピーして起動する

```Dockerfile
# golang をビルド
FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

# 上でビルドしたバイナリだけを Alpine Linux イメージにコピー・起動
FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]  
```

さらに，イメージからコピーするだけであれば，下記のように簡略化できます。

例：Nginx のベースイメージ内の nginx.conf だけコピーしてくる

```Dockerfile
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
```


### 解決法の具体例

前述した PHP-FPM の例は以下のように書き直せます。

before （再掲）

```Dockerfile
FROM php:7.3-fpm

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
```

after

```Dockerfile
FROM php:7.3-fpm

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
```


## 参考
- [Dockerにcomposerをインストールする方法の正解](https://blog.hanhans.net/2019/01/08/docker-composer/)
- [Composer](https://getcomposer.org/download/)
- [Use multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/)
- [Composer Docker Official Images](https://hub.docker.com/_/composer)


## 謝辞
参考にさせていただいた記事「[Dockerにcomposerをインストールする方法の正解](https://blog.hanhans.net/2019/01/08/docker-composer/)」の hanhan 様に感謝いたします。