はじめに

Nginx は便利な HTTP サーバで、超お手軽にコンテナで起動して Web サービスを提供することができる。
一方で、コンテナ運用のベストプラクティスに従うには若干クセがある。

何がクセがあるのか

コンテナ運用のベストプラクティスは以下だ。

  • コンテナは各環境(開発、プロダクションなど)でイメージを変えない
  • 変えるのは環境変数で、環境変数によってコンテナ内の動作を制御する

一方で、Nginx は環境変数を渡すのが難しい。
以下の記事のような小細工をしなくてはいけない。

【Qiita】envsubstを使ってDockerで設定ファイルに環境変数を埋め込めこむ汎用的なパターン

しかし、最近の Nginx コンテナではこんなことをしなくても良くなっているのだ!

docker-entrypoint.sh の登場

Nginx コンテナを起動すると、デフォルトでは docker-entrypoint.sh が実行される。

このスクリプトの内容は下記の通りだ(FROM nginx:1.18 した場合のイメージ)

docker-entrypoint.sh
#!/bin/sh
# vim:sw=4:ts=4:et

set -e

if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
exec 3>&1
else
exec 3>/dev/null
fi
if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then
if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
echo >&3 "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration"

        echo >&3 "$0: Looking for shell scripts in /docker-entrypoint.d/"
        find "/docker-entrypoint.d/" -follow -type f -print | sort -n | while read -r f; do
case "$f" in
                *.sh)
                    if [ -x "$f" ]; then
echo >&3 "$0: Launching $f";
                        "$f"
                    else
                        # warn on shell scripts without exec bit
                        echo >&3 "$0: Ignoring $f, not executable";
                    fi
                    ;;
                *) echo >&3 "$0: Ignoring $f";;
            esac
        done
echo >&3 "$0: Configuration complete; ready for start up"
    else
echo >&3 "$0: No files found in /docker-entrypoint.d/, skipping configuration"
    fi
fi
exec "$@"

要するに、/docker-entrypoint.d/ 配下にある .sh を順次実行してくれる(.sh 以外は無視される)。

さらに、/docker-entrypoint.d/ にはデフォルトで以下のスクリプトが含まれている。

20-envsubst-on-templates.sh
#!/bin/sh

set -e

ME=$(basename $0)

auto_envsubst() {
  local template_dir="${NGINX_ENVSUBST_TEMPLATE_DIR:-/etc/nginx/templates}"
  local suffix="${NGINX_ENVSUBST_TEMPLATE_SUFFIX:-.template}"
  local output_dir="${NGINX_ENVSUBST_OUTPUT_DIR:-/etc/nginx/conf.d}"

  local template defined_envs relative_path output_path subdir
  defined_envs=$(printf '${%s} ' $(env | cut -d= -f1))
  [ -d "$template_dir" ] || return 0
  if [ ! -w "$output_dir" ]; then
echo >&3 "$ME: ERROR: $template_dir exists, but $output_dir is not writable"
    return 0
  fi
find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do
relative_path="${template#$template_dir/}"
    output_path="$output_dir/${relative_path%$suffix}"
    subdir=$(dirname "$relative_path")
    # create a subdirectory where the template file exists
    mkdir -p "$output_dir/$subdir"
    echo >&3 "$ME: Running envsubst on $template to $output_path"
    envsubst "$defined_envs" < "$template" > "$output_path"
  done
}

auto_envsubst

exit 0

これは、要するに全部の環境変数で、/etc/nginx/templates 配下の *.template ファイルの中身の環境変数定義(${ }で囲まれた文字列)を置換してくれるというものだ。

これで環境変数問題は解決か?

解決してない。

↑のスクリプトを読んで分かる通り、設定されている環境変数をすべて置換してしまう。
ログ出力等で環境変数と nginx.conf の変数が重複している場合、誤爆してしまう可能性がある。

もし誤爆を回避したいのであれば、結局、自分で envsubst を使ったスクリプトを用意するしかない。

また、/docker-entrypoint.d/ 配下に上記のスクリプトを追加すれば良い感じに起動時に実行してくれるのだが、docker-entrypoint.sh は、呼び出したスクリプトがエラーになっても処理を続行してしまうので、例えば「この環境変数が定義されていない場合は起動しない」といったことができない。

結局、CMD で上記スクリプトを実行するしかなく、docker-entrypoint.sh で完結するのは難しそうなのであった……