PHPの公式Dockerfileを読み解く

「Dockerfileをスラスラ書けるようになるにはどうすればいいか？」

自分の答えは、**公式のDockerfileを読み解くこと**だと思う。公式のリポジトリには、DockerやLinuxに精通した人達の知恵が詰まっており、知らなかったことを多く知れる。

ということで今回は、`php:7.3-apache-stretch` という「Apache上でPHP7.3を動かす環境」が入ったDockerイメージのDockerfileを読解していく。
GitHubはここにある。[php/7.3/stretch/apache · docker-library/php · GitHub](https://github.com/docker-library/php/tree/1eb2c0ab518d874ab8c114c514e16aa09394de14/7.3/stretch/apache)

## ベースイメージの指定（debian:stretch-slim）

```Dockerfile
FROM debian:stretch-slim
```

Dockerfileは全て`FROM`から始まり、ベースのイメージを設定する。ここではベースイメージに`Debian v9(stretch)`を使用している。

[Linuxディストリビューションのトレンド](https://w3techs.com/technologies/history_details/os-linux)によると、**UbuntuとDebianが大きくシェア**を占めている。UbuntuはDebianを元にしており、Debianとほぼ同じように扱える。debianの方が軽量でサーバー向けという位置付けになる。
`stretch`については、Debianの各メジャーバージョンに付けられるコードネームのことであり、現在の最新安定バージョンである。他にも前バージョンの`jessie`や次バージョンの`buster`があり、他のバージョンも「[DebianReleases - Debian Wiki](https://wiki.debian.org/DebianReleases)」で確認できる。

## パッケージの制御（/etc/apt/preferences.d）

```Dockerfile
# prevent Debian's PHP packages from being installed
# https://github.com/docker-library/php/pull/542
RUN set -eux; \
	{ \
		echo 'Package: php*'; \
		echo 'Pin: release *'; \
		echo 'Pin-Priority: -1'; \
	} > /etc/apt/preferences.d/no-debian-php
```

Debianではパッケージ管理システムに**APT (Advanced Packaging Tool)**を使い、APTのコマンドとして`apt-get`が使われる。

しかし中にはソースからビルドしたものを使い、APTからはインストールして欲しくない時がある。その際に`/etc/apt/preferences.d`で、特定のパッケージのインストールを制御することができる。今回だと**パッケージ名がphpで始まるパッケージは絶対にインストールしない**という設定になっている。より詳しい設定は[Debian(Ubuntu)で apt-get upgrade で自動更新したくない場合の対応](https://server-setting.info/debian/debian-no-apt-upgrade.html)が参考になる。

また最初に`set`コマンドによってシェルに関する設定を行っている。`-e`オプションによって実行したコマンドが1つでもエラーになれば直ちに終了するようにし、`-u`オプションで未定義の変数などを使おうとすればエラーにするようにしている。`-x`オプションでは実行コマンドとその引数をトレースとして出力するようにしている。
この**`set`コマンドはDockerfileで頻出するコマンド**であり、今回のDockerfileでも頻繁に使われている。

またLinuxコマンドにおける`;`や`&&`の意味がわからなければ、[Linuxコマンドを連続して使うには - Qiita](https://qiita.com/egawa_kun/items/714394609eef6be8e0bf)が参考になる。

## phpizeに必要なパッケージ（$PHPIZE_DEPS）

```Dockerfile
# dependencies required for running "phpize"
# (see persistent deps below)
ENV PHPIZE_DEPS \
		autoconf \
		dpkg-dev \
		file \
		g++ \
		gcc \
		libc-dev \
		make \
		pkg-config \
		re2c
```

ここではPHPの拡張モジュールのビルドツールである**phpize**で必要なパッケージリストを環境変数に設定している。この環境変数は後ほど`apt-get`で使われる。

それぞれのパッケージについての説明を以下に記載する。

- autoconf（configureというパッケージインストールスクリプトを作成するためのパッケージ）
- dpkg-dev（APTより低水準なdebianのパッケージ管理システム）
- file（ファイルの形式などを調べるfileコマンド）
- g++（C++ コンパイラ）
- gcc（C コンパイラ）
- libc-dev（C言語 標準ライブラリ
- make（Makefileというファイルを基にコンパイルを行うツール）
- pkg-config（コンパイルする際に必要なライブラリの情報を取得するツール）
- re2c（CとC++のための字句解析ツール）

## パッケージのインストール（apt-get install）

```Dockerfile
# persistent / runtime deps
RUN apt-get update && apt-get install -y \
		$PHPIZE_DEPS \
		ca-certificates \
		curl \
		xz-utils \
	--no-install-recommends && rm -r /var/lib/apt/lists/*
```

ここでは先ほどの$PHPIZE_DEPSとその他のパッケージをインストールしている。
**`apt-get update`はインストール可能なパッケージリストを更新**するコマンドである。実際にパッケージのアップデートを行いたい場合は`apt-get upgrade`を用いる。

`update`は、`/etc/apt/sources.list`に書かれているURLからインストール可能なパッケージを`/var/lib/apt/lists`に保存する。

```/etc/apt/sources.list
deb http://deb.debian.org/debian stretch main
deb http://security.debian.org/debian-security stretch/updates main
deb http://deb.debian.org/debian stretch-updates main
```

`apt-get install -y`は列挙するパッケージをインストールするコマンドである。`-y`オプションが無い場合はインストール前に「インストールしていいですか」という類の確認が出るが、Dockerfileの場合はこういったインタラクティブなコマンド処理は行えないため、`-y`オプションによってスキップしている。
`--no-install-recommends`オプションは、**おすすめの関連パッケージをインストールさせない**ためのオプションである。余計なパッケージを入れられるとDockerのイメージサイズが大きくなるため、基本的にインストールしない方が良い。

インストール後は、`rm -r /var/lib/apt/lists/*`を行い、**`apt-get update`で取得したパッケージのソースを削除**する。これらのソースはインストール後に使うことは無いため、イメージサイズの軽量化のために削除する。

今回登場したパッケージの説明も記載しておく。

- ca-certificates（認証局（CA：Certification Authority）の証明書などを含んだパッケージ）
- curl（httpを始めとした様々なプロトコルで通信を行うためのツール）
- xz-utils（xzという圧縮フォーマットのファイルを作成・展開するためのツール）

## PHP_INI_DIR

```Dockerfile
ENV PHP_INI_DIR /usr/local/etc/php
RUN set -eux; \
	mkdir -p "$PHP_INI_DIR/conf.d"; \
```

PHPでは`.ini`ファイルを設定ファイルとして扱う。これらの保存ディレクトリを作成する。$PHP_INI_DIRは、また後ほどPHPのビルド時に使用する。

## Apache root directory

```bash
# allow running as an arbitrary user (https://github.com/docker-library/php/issues/743)
	[ ! -d /var/www/html ]; \
	mkdir -p /var/www/html; \
	chown www-data:www-data /var/www/html; \
	chmod 777 /var/www/html
```

ここではApacheをwww-dataユーザーから実行できるようにするための処理を行う。www-dataはApacheを実行する際のデフォルトの実行ユーザーである。

`[ ! -d /var/www/html ]`は、**もし`/var/www/html`がディレクトリではなかったら**という意味であり、真であれば続くコマンドが実行される。
chownによって`/var/www/html`の所有者をwww-dataにし、chmodによってどのユーザーでも`/var/www/html`を読み書きや実行してもいい、という権限にしている。

## Apache2のインストール

```Dockerfile
ENV APACHE_CONFDIR /etc/apache2
ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars
```

Apacheの設定ファイルのディレクトリなどを環境変数に設定する。これは後ほど利用する。

```Dockerfile
RUN set -eux; \
	apt-get update; \
	apt-get install -y --no-install-recommends apache2; \
	rm -rf /var/lib/apt/lists/*; \
```

特に目新しいことはしておらず、apache2をapt-getによってインストールしているだけである。

```bash
sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
```

~~ここの`sed`による処理は正直よくわからない。~~

@taterenさんよりコメントを頂きました。
ここの`sed`の処理は、APACHE_RUN_USERが指定されていれば`www-data`ではなく、そちらを優先する処理のもようです。

```bash
	. "$APACHE_ENVVARS"; \
	for dir in \
		"$APACHE_LOCK_DIR" \
		"$APACHE_RUN_DIR" \
		"$APACHE_LOG_DIR" \
	; do \
		rm -rvf "$dir"; \
		mkdir -p "$dir"; \
		chown "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$dir"; \
# allow running as an arbitrary user (https://github.com/docker-library/php/issues/743)
		chmod 777 "$dir"; \
	done; \
```

Apacheで利用する各ディレクトリを再作成し、適切なパーミッションに設定している。

```bash
# delete the "index.html" that installing Apache drops in here
	rm -rvf /var/www/html/*; \
```

Apacheインストール時に作成されるindex.htmlを削除している。

```bash
# logs should go to stdout / stderr
	ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
	ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
	ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
	chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"
```

Apacheのログを標準出力・標準エラー出力に出力するように設定している。

lnコマンドによってリンクを作成することができる。`-s`オプションでハードリンクではなくシンボリックリンクにし、`-f`オプションで同じファイルがあった場合でも強制的に上書きし、最後に`-T`オプションでリンク先をディレクトリではなく通常ファイルとして扱うようにしている。

chownの`-R`オプションで再帰的に、そして`--no-dereference`オプションでシンボリックリンク自体の所有者を変更するようにしている。

```Dockerfile
# Apache + PHP requires preforking Apache for best results
RUN a2dismod mpm_event && a2enmod mpm_prefork
```

Apacheでは`a2dismod`コマンドでモジュールを無効化したり、`a2enmod`コマンドで有効化したりできる。
そして今回の2つのモジュールは、ApacheのMPM (Multi Processing Module)というApacheの並行処理方法に関するモジュールのことである。

この辺りはマルチプロセスやマルチスレッドなどの知識が必要となる。その辺りは[Apache MPMとはなんぞやという話 - 備忘録の裏のチラシ](https://norikone.hatenablog.com/entry/2016/01/21/Apache2.4%E3%81%AEMPM%E5%91%A8%E3%82%8A)が参考になる。**PHPはスレッドセーフな言語ではない**ため、マルチプロセスで動かさなければならない。そのためmpm_preforkをモジュールを利用して、リクエストを処理するApacheのプロセスをあらかじめforkするようなマルチプロセス処理形態にする。

```Dockerfile
# PHP files should be handled by PHP, and should be preferred over any other file type
RUN { \
		echo '<FilesMatch \.php$>'; \
		echo '\tSetHandler application/x-httpd-php'; \
		echo '</FilesMatch>'; \
		echo; \
		echo 'DirectoryIndex disabled'; \
		echo 'DirectoryIndex index.php index.html'; \
		echo; \
		echo '<Directory /var/www/>'; \
		echo '\tOptions -Indexes'; \
		echo '\tAllowOverride All'; \
		echo '</Directory>'; \
	} | tee "$APACHE_CONFDIR/conf-available/docker-php.conf" \
	&& a2enconf docker-php
```

ここではApacheが`.php`ファイルを処理するための設定ファイルを作成している。
FilesMatchディレクティブで、リクエストファイルがphpファイルであれば`application/x-httpd-php`というハンドラで処理するようにしている。この辺りはApacheの話になってしまうので詳しい説明はしない。

また公式では設定ファイルを愚直にechoしているが、別ファイルに切り出してDockerの`COPY`コマンドを使う方が簡潔になることもある。

```Dockerfile
ENV PHP_EXTRA_BUILD_DEPS apache2-dev
ENV PHP_EXTRA_CONFIGURE_ARGS --with-apxs2 --disable-cgi
```

後ほどPHPをビルドする際に利用する環境変数を設定している。

## PHPのインストール

### PHPのソースをダウンロード

```Dockerfile
# Apply stack smash protection to functions using local buffers and alloca()
# Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64)
# Enable optimization (-O2)
# Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default)
# Adds GNU HASH segments to generated executables (this is used if present, and is much faster than sysv hash; in this configuration, sysv hash is also generated)
# https://github.com/docker-library/php/issues/272
ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2"
ENV PHP_CPPFLAGS="$PHP_CFLAGS"
ENV PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie"
```

コンパイルで用いられる最適化のオプション。正直よくわからないよな？俺もそう。

```Dockerfile
ENV GPG_KEYS CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D
```

ダウンロードしたPHPのソースが改ざんされていないかをチェックするには、**gpg (GNU Privacy Guard)**と呼ばれる暗号化ソフトウェアが使われる。この$GPG_KEYSは後ほどこのgpgによって使うフィンガープリント（ハッシュ関数で算出したハッシュ値）である。

```Dockerfile
ENV PHP_VERSION 7.3.1
ENV PHP_URL="https://secure.php.net/get/php-7.3.1.tar.xz/from/this/mirror" PHP_ASC_URL="https://secure.php.net/get/php-7.3.1.tar.xz.asc/from/this/mirror"
ENV PHP_SHA256="cfe93e40be0350cd53c4a579f52fe5d8faf9c6db047f650a4566a2276bf33362" PHP_MD5=""
```

PHPのバージョンと、PHPのダウンロードURL、そしてソースのハッシュ値を定義している。
これは[PHP: Downloads](https://php.net/downloads.php)に記載されており、旧バージョンについても[PHP: Releases](https://secure.php.net/releases/)から探すことができる。

```Dockerfile
RUN set -xe; \
	\
	fetchDeps=' \
		wget \
	'; \
	if ! command -v gpg > /dev/null; then \
		fetchDeps="$fetchDeps \
			dirmngr \
			gnupg \
		"; \
	fi; \
	apt-get update; \
	apt-get install -y --no-install-recommends $fetchDeps; \
	rm -rf /var/lib/apt/lists/*; \
```

fetchDepsという変数に`apt-get install`するものを格納している。wgetはファイルをダウンロードする際に用いるツールである。
`gpg`コマンドが存在しなければfetchDepsにgnupgとdirmngr（証明書の管理ツール）を追加している。

```bash
	mkdir -p /usr/src; \
	cd /usr/src; \
	\
	wget -O php.tar.xz "$PHP_URL"; \
	\
	if [ -n "$PHP_SHA256" ]; then \
		echo "$PHP_SHA256 *php.tar.xz" | sha256sum -c -; \
	fi; \
	if [ -n "$PHP_MD5" ]; then \
		echo "$PHP_MD5 *php.tar.xz" | md5sum -c -; \
	fi; \
```

`/usr/src`ディレクトリを作成し移動している。このディレクトリは慣用的にソースを置く場所となっている。
次に`wget`コマンドで$PHP_URLからPHPのソースを`php.tar.xz`として保存している。

$PHP_SHA256が設定されていれば、`sha256sum -c`によってソースのハッシュ値と比較する。もし**違っていれば改ざんされている可能性がある**ため、エラーが返ってくる。これは$PHP_MD5でも同じである。

```bash
	if [ -n "$PHP_ASC_URL" ]; then \
		wget -O php.tar.xz.asc "$PHP_ASC_URL"; \
		export GNUPGHOME="$(mktemp -d)"; \
		for key in $GPG_KEYS; do \
			gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
		done; \
		gpg --batch --verify php.tar.xz.asc php.tar.xz; \
		command -v gpgconf > /dev/null && gpgconf --kill all; \
		rm -rf "$GNUPGHOME"; \
	fi; \
```

こちらはgpg、すなわち**電子署名を用いてソースが改ざんされていないかを確認**する。基本的にハッシュ値の比較だけでもセキュリティ的に十分だが、PHPの公式である[PHP: Downloads](https://php.net/downloads.php)まで改ざんされているケースを想定し、さらに厳重に電子署名でチェックしている。gpgの一連の認証処理についてはここでは省略するため、詳しくは別途調べてほしい。

```bash
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $fetchDeps
```

先ほどインストールした$fetchDepsはもう使わないため、`apt-get purge`によりアンインストールしている。`APT::AutoRemove::RecommendsImportant`オプションをfalseにすることで、$fetchDepsの依存関係にあるパッケージを削除している。

### PHPのビルド

```Dockerfile
COPY docker-php-source /usr/local/bin/
```

docker-php-sourceというシェルスクリプトをコピーしている。このシェルスクリプトはPHPのソースを解凍したり削除したりする処理をまとめている。

```Dockerfile
RUN set -eux; \
	\
	savedAptMark="$(apt-mark showmanual)"; \
	apt-get update; \
	apt-get install -y --no-install-recommends \
		libcurl4-openssl-dev \
		libedit-dev \
		libsodium-dev \
		libsqlite3-dev \
		libssl-dev \
		libxml2-dev \
		zlib1g-dev \
		${PHP_EXTRA_BUILD_DEPS:-} \
	; \
```

`apt-mark showmanual`はAPTによって手動でインストールしたパッケージリストを取得するコマンドである。これをsavedAptMarkとして保持しておき、これからインストールするビルドにしか使わないパッケージ群だけを削除する際に用いる。

今回`apt-get install`するパッケージの簡単な説明を記載する。

- libcurl4-openssl-dev（SSL/TLS通信に必要なパッケージ）
- libedit-dev（改行処理や履歴に関するパッケージ?）
- libsodium-dev（暗号化やハッシュ計算などを提供するパッケージ）
- libsqlite3-dev（SQLiteという組み込み型DBのパッケージ）
- libssl-dev（SSL/TLSの暗号化プロトコルに必要なパッケージ）
- libxml2-dev（XMLを使うためのパッケージ）
- zlib1g-dev（deflateと呼ばれる圧縮法を実装したライブラリ）

$PHP_EXTRA_BUILD_DEPSにはApacheのインストール時にapache2-devを設定している。apache2-devはApache上でPHPを動かすために用いられる。

```Dockerfile
sed -e 's/stretch/buster/g' /etc/apt/sources.list > /etc/apt/sources.list.d/buster.list;
```

**`sed`は置換処理を行う**コマンドであり、`'s/stretch/buster/g'`と書くことで、stretchという文字列を全てbusterに置換することができる。そして置換後のテキストを`/etc/apt/sources.list.d/buster.list`にファイルとして保存することで、新たにパッケージのインストールを制御している。

**busterとはstretchの次バージョン**であり、2019年2月現在ではtesting状態となっている。stretchに存在しない新しいパッケージをインストールしたい場合、このようにパッケージのダウンロード先URLを追加する。

```bash
	{ \
		echo 'Package: *'; \
		echo 'Pin: release n=buster'; \
		echo 'Pin-Priority: -10'; \
		echo; \
		echo 'Package: libargon2*'; \
		echo 'Pin: release n=buster'; \
		echo 'Pin-Priority: 990'; \
	} > /etc/apt/preferences.d/argon2-buster; \
	apt-get update; \
	apt-get install -y --no-install-recommends libargon2-dev; \
```

`/etc/apt/preferences.d`の設定は冒頭でも説明したので省略する。中身を要約すると、**コードネームがbusterのパッケージはlibargon2\*以外インストールしない**という意味である。argon2はパスワードのハッシュ関数であり、PHP7.2から導入されている。

```bash
	rm -rf /var/lib/apt/lists/*; \
	\
	export \
		CFLAGS="$PHP_CFLAGS" \
		CPPFLAGS="$PHP_CPPFLAGS" \
		LDFLAGS="$PHP_LDFLAGS" \
	; \
	docker-php-source extract; \
	cd /usr/src/php; \
	gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
	debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \
# https://bugs.php.net/bug.php?id=74125
	if [ ! -d /usr/include/curl ]; then \
		ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \
	fi; \
```

ビルド時のオプションを環境変数や変数に設定したり、PHPのソースを解凍したりしている。

```bash
	./configure \
		--build="$gnuArch" \
		--with-config-file-path="$PHP_INI_DIR" \
		--with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \
		\
# make sure invalid --configure-flags are fatal errors intead of just warnings
		--enable-option-checking=fatal \
		\
# https://github.com/docker-library/php/issues/439
		--with-mhash \
		\
# --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236)
		--enable-ftp \
# --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195)
		--enable-mbstring \
# --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself)
		--enable-mysqlnd \
# https://wiki.php.net/rfc/argon2_password_hash (7.2+)
		--with-password-argon2 \
# https://wiki.php.net/rfc/libsodium
		--with-sodium=shared \
		\
		--with-curl \
		--with-libedit \
		--with-openssl \
		--with-zlib \
		\
# bundled pcre does not support JIT on s390x
# https://manpages.debian.org/stretch/libpcre3-dev/pcrejit.3.en.html#AVAILABILITY_OF_JIT_SUPPORT
		$(test "$gnuArch" = 's390x-linux-gnu' && echo '--without-pcre-jit') \
		--with-libdir="lib/$debMultiarch" \
		\
		${PHP_EXTRA_CONFIGURE_ARGS:-} \
	; \
```

長ったらしいが要は**configureというシェルスクリプトを実行しているだけ**である。このconfigureスクリプトはPHPのソースに含まれており、インストールに必要なライブラリのチェックとMakefileの生成を行う。Makefileファイルは`make`というコンパイルを行うコマンドで用いる。

configureスクリプトの実行時に様々なオプションを指定している。`--with-config-file-path`でPHPの設定ファイルディレクトリを指定したり、`--with-openssl`でPHPがOpenSSLをサポートできるようにしたりしている。


```bash
	make -j "$(nproc)"; \
	make install; \
	find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; \
	make clean; \
```

`make`でPHPのコンパイルを行う。`-j`オプションでコンパイルを実行するジョブ数を指定することができ、`nproc`コマンドで取得できるCPU数をそのまま渡している。`make install`は`make`でビルドしたバイナリなどを規定のディレクトリに移動させるコマンドである。

`find`コマンドはファイルやディレクトリを検索するコマンドであり、今回は実行可能なバイナリファイルを列挙して、それを`strip`コマンドの引数として渡している。`strip --strip-all`コマンドは渡されたファイルのシンボルテーブル（デバッグ用に使われるデータ）を全て削除し、実行ファイルのサイズを軽量化している。

`make clean`コマンドは、コンパイル時に生成したもう使わないファイルなどを削除する。

```bash
# https://github.com/docker-library/php/issues/692 (copy default example "php.ini" files somewhere easily discoverable)
	cp -v php.ini-* "$PHP_INI_DIR/"; \
	\
	cd /; \
	docker-php-source delete; \
```

PHPのソースには[php.ini-development](https://github.com/php/php-src/blob/master/php.ini-development)や[php.ini-production](https://github.com/php/php-src/blob/master/php.ini-production)といった初期iniファイルがあるので、それをコピーしている。あとは`docker-php-source delete`でPHPのソースを削除する。

```bash
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
	apt-mark auto '.*' > /dev/null; \
	[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \
	find /usr/local -type f -executable -exec ldd '{}' ';' \
		| awk '/=>/ { print $(NF-1) }' \
		| sort -u \
		| xargs -r dpkg-query --search \
		| cut -d: -f1 \
		| sort -u \
		| xargs -r apt-mark manual \
	; \
	apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
```

ビルド時のみ必要だったパッケージを`apt-get purge`で削除している。これもイメージ軽量化の技の一つ。

```bash
	php --version; \
	\
# https://github.com/docker-library/php/issues/443
	pecl update-channels; \
	rm -rf /tmp/pear ~/.pearrc
```

phpのバージョンを表示し、PHPのインストールが正常にできているかを確認している。また`pecl update-channels`でpeclのリポジトリを更新している。

```Dockerfile
COPY docker-php-ext-* docker-php-entrypoint /usr/local/bin/
```

`docker-php-ext-*`はPHPの拡張機能をインストールするためのスクリプトである。これは[公式リポジトリ内](https://github.com/docker-library/php/tree/1eb2c0ab518d874ab8c114c514e16aa09394de14/7.3/stretch/apache)で用意されている。自分たちが使うのは基本的に`docker-php-ext-install`というスクリプトである。これはDockerfile内で`RUN docker-php-ext-install curl`のように使えば、PHPのcurl拡張機能がインストールされる。

```Dockerfile
# sodium was built as a shared module (so that it can be replaced later if so desired), so let's enable it too (https://github.com/docker-library/php/issues/598)
RUN docker-php-ext-enable sodium
```

sodiumというPHPにおける暗号ライブラリを有効化している。PHP7.2では標準で組み込まれているが、有効化するためにenableスクリプトを実行している。

```Dockerfile
ENTRYPOINT ["docker-php-entrypoint"]
##<autogenerated>##
COPY apache2-foreground /usr/local/bin/
WORKDIR /var/www/html

EXPOSE 80
CMD ["apache2-foreground"]
##</autogenerated>##
```

締めはApache2をフォアグラウンドで実行するスクリプトを実行している。

## これにて完了
以上がPHPの公式Dockerfileの読解になる。

読むきっかけとなったのは、PHP5.3とApacheを動かすイメージが必要だったのが、公式から5.3が削除されており、そして有志が作ったものも少し古かったためである。だが**この読解を通して、無事にPHP5.3でビルドすることができた。**

独自のDockerfileをスラスラと書けるようになりたい人は、ぜひ公式のDockerfileを読んで欲しい。