docker importで作るイメージをAutomated Builds(Dockerfile)で作る方法

Docker Hubにある非公式のイメージを使用する場合、それがどうやって作られているか気になりますよね？Automated Buildsであれば、Dockerfileから生成されているので、何をやっているかがわかりますが、直接イメージがpushされている場合、なにが含まれているかわかりません。Automated Buildsなら確実に信用できるというわけではありませんが、ビルド手順を公開し自動的に構築することで利用者の不安を軽減することが出来ます。

## Automated Buildsにしていなかった理由

私は基本的にAutomated Buildsにしているのですが、これまでしていなかったのがOpenWrtのDockerイメージのビルドです。私は[shellspec](https://github.com/shellspec/shellspec)というシェルスクリプトのテスティングフレームワークを開発しているのですが、そのテスト環境の一つとしてOpenWrtを使用しています。Docker Hubには[OpenWrtが公開しているイメージ](https://hub.docker.com/r/openwrtorg/rootfs)があるのですが最新バージョンしかありません。古いバージョンでもテストしたかったので独自でイメージを作成しています。

OpenWrtが公開している[手順](https://openwrt.org/docs/guide-user/virtualization/docker_openwrt_image)にも書いてありますが、手元でやるならファイルをダウンロードして`docker import`で簡単にイメージが作れます。

```
$ docker import http://downloads.openwrt.org/attitude_adjustment/12.09/x86/generic/openwrt-x86-generic-rootfs.tar.gz openwrt-x86-generic-rootfs
```

しかし、Automated BuildsではDockerfileから生成します。Dockerfileの`FROM`にtar.gzは指定できませんし、`ADD`にURLを指定した場合、tar.gzは展開されません。（ローカルファイルからtar.gzをADDした場合は展開されます。以前はバグでURLからADDした場合も展開されていたようで、OpenWrtのサイトにはその手順が残っていますが今はこのやり方はできません。）そのため今までは手元で作成したdockerイメージをpushしていました。

## どうやってAutomated Buildsにしたか？

**2020-01-22 実装方法を変更しました。** （古いやり方は下の方に残しています。）

この記事を最初に投稿した時はDocker Hubのフックの機能していたのですが、マルチステージビルドを使用した方法に変更しました。フックを使用した場合に比べてDockerfile一つで完結するのでローカルでもいつも通りのやり方でビルドできるというメリットがあります。またその際にハッシュチェックも行いより安心して使えるようにしています。

### 実装

まずマルチステージビルドの一段目で`.tar.gz`ファイルを取得してディレクトリに展開します。その際にハッシュチェックも行います。マルチステージビルドの二段目で`FROM scratch`から始めて一段目のディレクトリをコピーしています。

```Dockerfile:Dockerfile
FROM alpine as builder
ENV base=https://downloads.openwrt.org/releases/19.07.0-rc2/targets/x86/64/
ENV file=openwrt-19.07.0-rc2-x86-64-generic-rootfs.tar.gz
ENV sha256=0910f4c4c0aa009117ecd97bb0d047d53229f58a889c905525f62b64595ab666
RUN wget -q "$base$file" && echo "$sha256  $file" | sha256sum -c
RUN mkdir /rootfs && tar xf "$file" -C /rootfs

FROM scratch
COPY --from=builder /rootfs/ /
CMD ["/bin/sh"]
```

----

**以下は古いやり方です。**

## どうやってAutomated Buildsにしたか？（旧）

使用した機能は[Custom build phase hooks](https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks)です。Docker HubのAutomated Buildsはbuildやpushの前後等にフックを入れることが出来ます。またビルド処理（`docker build`コマンドの実行）そのものを入れ替えることも出来ます。これを利用してビルドの前（`hooks/pre_build`）でtar.gzファイルをダウンロードして、このファイルをDockerfileで`ADD`しています。

### 実装

Automated BuildsはGitHubやBitbucketと連携してソースコードをpushしたタイミングで、ソースコードのDockerfileを元にDocker HubがDockerイメージをビルドします。フックスクリプトも同じくソースコードのリポジトリに含めます。今回必要なのはビルド前のフックなので（Dockerfileと同じ階層に）`hooks/pre_build`を作成します。

まず先に`Dockerfile`の内容です。

```Dockerfile:Dockerfile
#IMPORT https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz
FROM scratch
ADD imports/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz /
CMD /bin/sh
```

`#IMPORT`で始まる行を参照し、そのURLからファイルをダウンロードして`imports`ディレクトリに保存するという処理を`hooks/pre_build`で行っています。（マルチステージビルドの事も考えて`#IMPORT`は複数記述できるようにしています。）


```sh:hooks/pre_build
#!/bin/sh -eu

while read -r cmd path; do
  [ "$cmd" = "#IMPORT" ] || continue
  curl -vsSfL "$path" -o "imports/${path##*/}"
done < "$DOCKERFILE_PATH"
```

### フックについて

フックではファイルをダウンロードするだけでなく`apt-get`も使用できたので自由度はかなり高いです。ディストリは現時点ではUbuntu 16.04 xenialが使われていました。フックを利用することで複雑なビルドでもAutomated Buildsにすることができるでしょう。