Help us understand the problem. What is going on with this article?

Docker上のNginxのconfに環境変数(env)を渡すたったひとつの全く優れてない方法(修正:+優れている方法)

More than 3 years have passed since last update.

Dockerでnginxを起動させるときに、以下のようなたとえばPHP-FPMへのPorxy設定を行ったConfを設定することはままあるかと思います。(別にPHP-FPMじゃなくてもいいです。)

server {
    listen 80 default;
    server_name _;
    root /var/www/html;
    index index.php index.html index.htm;
    charset utf-8;

    access_log off;
    error_log off;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass ${PHP_APP_HOST}:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

この例の fastcgi_pass ${PHP_APP_HOST}:9000; のように、設定値の一部を環境変数から渡したいことがまぁあるかと思います。
「いや、そんなんDockerfileにsed書いてビルド(docker build)すりゃええやん」って思うかもしれませんが、buildもpushも面倒なので docker-compose.ymlenvironment のとこだけ書き換えりゃいい感じになる世界観が欲しいことってありますよね。ね。

公式の解決策

もちろんこんな事はみんな思うわけで、workaroundがdockerhubのnginxのドキュメントに書いてありました。以下引用です。

https://hub.docker.com/_/nginx/

using environment variables in nginx configuration

Out-of-the-box, Nginx doesn't support using environment variables inside most configuration blocks. But envsubst may be used as a workaround if you need to generate your nginx configuration dynamically before nginx starts.

Here is an example using docker-compose.yml:

image: nginx
volumes:
- ./mysite.template:/etc/nginx/conf.d/mysite.template
ports:
- "8080:80"
environment:
- NGINX_HOST=foobar.com
- NGINX_PORT=80
command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

The mysite.template file may then contain variable references like this :
listen ${NGINX_PORT};

WAO!!!何てCoolなんだ!!!早速やってみるぜ!!!!!

やってみる

こんな感じの docker-compose.yml にしてみました。

web:
  image: localhost:5000/web
  ports:
    - 80:80
  expose:
    - 80
  links:
    - app:php
  environment:
    - PHP_APP_HOST=php
  command: /bin/sh -c "envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

app:
  image: localhost:5000/app
  expose:
    - 9000

ポチーっとした結果、

web_1  | 2016/06/25 02:43:39 [emerg] 6#6: invalid number of arguments in "fastcgi_param" directive in /etc/nginx/conf.d/default.conf:23
web_1  | nginx: [emerg] invalid number of arguments in "fastcgi_param" directive in /etc/nginx/conf.d/default.conf:23

Ah han...
何ぞ、と思ってconfをcatしてみると ( command: /bin/sh -c "envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && cat /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" )

web_1  | server {
web_1  |     listen 80 default;
web_1  |     server_name _;
web_1  |     root /var/www/html;
web_1  |     index index.php index.html index.htm;
web_1  |     charset utf-8;
web_1  |
web_1  |     access_log off;
web_1  |     error_log off;
web_1  |
web_1  |     location / {
web_1  |         try_files  / /index.php;
web_1  |     }
web_1  |
web_1  |     location ~ \.php$ {
web_1  |         fastcgi_pass php:9000;
web_1  |         fastcgi_index index.php;
web_1  |         fastcgi_param SCRIPT_FILENAME  ;
web_1  |         include       fastcgi_params;
web_1  |     }
web_1  | }

oh... $xxx 系のやつが全部消えてますやん・・・

envsubst の挙動を確認

$ envsubst --help
使用法: envsubst [オプション] [シェル形式]

環境変数の値を代入.

オペレーションモード:
  -v, --variables             シェル形式に現れる変数を出力

有益な出力:
  -h, --help                  このヘルプを表示して終了
  -V, --version               バージョン情報を出力して終了

通常のオペレーションモードでは, 標準入力が標準出力にコピーされ, $VARIABLE
または ${VARIABLE} 形式の環境変数を参照し, それぞれの値に置換されます.
シェル形式が指定されると, シェル形式で参照される, そのような環境変数だけが
代入されますが, そうでない場合は標準入力に現れる全ての環境変数が代入されます.

--variables が使われると, 標準入力は無視され, 出力は 1行ずつシェル形式で
参照される環境変数から構成されます.

バグレポートは <bug-gnu-gettext@gnu.org> まで.

おーん。

$ echo '${hoge}-${fuga}_$hoge-$fuga' | hoge='hello' envsubst
hello-_hello-

つまり $xxxx 形式も ${xxxx} 形式も全部 envsubst で処理されるし、設定されてない環境変数は無視されて空文字になるわけです。

たったひとつの全く優れてない方法

:exclamation: 注意: ブコメでイケてる方法を教えていただいたので、ここに書いてある方法はマジでイケてなかったです!イケてる方法は最後に追記しています。

いやマジでイケてないんですけど stackoverflow に書いてあった方法で何とかなったのでこれでいいかなと思いました。

参考: is there an escape character for envsubst?
http://stackoverflow.com/questions/24963705/is-there-an-escape-character-for-envsubst

これを参考にすると、以下のようなconfとymlでいい感じになりました。

conf

server {
    listen 80 default;
    server_name _;
    root /var/www/html;
    index index.php index.html index.htm;
    charset utf-8;

    access_log off;
    error_log off;

    location / {
        try_files ${DOLLAR}uri ${DOLLAR}uri/ /index.php${DOLLAR}is_args${DOLLAR}args;
    }

    location ~ \.php${DOLLAR} {
        fastcgi_pass ${PHP_APP_HOST}:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  ${DOLLAR}document_root${DOLLAR}fastcgi_script_name;
        include       fastcgi_params;
    }
}

もともと $ だった部分を全部 ${DOLLAR} に変えてます!!!COooooooolllllll

docker-compose.yml

web:
  image: localhost:5000/web
  ports:
    - 80:80
  expose:
    - 80
  links:
    - app:php
  environment:
    - PHP_APP_HOST=php
  command: /bin/sh -c "DOLLAR=$$ envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && cat /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

app:
  image: localhost:5000/app
  expose:
    - 9000

先頭に DOLLAR=$$ が追加されました!!やったぜ!!!

何をしてるのか

もうどうやっても envsubst$ は消えちゃうので ${DOLLAR} という $ を表す変数を作っちゃってそれで置換すれば大正義じゃねっていうやつ。

この状態で最終的に出力されたconfが以下のようになり、出力したい内容そのものにできました。

web_1  | server {
web_1  |     listen 80 default;
web_1  |     server_name _;
web_1  |     root /var/www/html;
web_1  |     index index.php index.html index.htm;
web_1  |     charset utf-8;
web_1  |
web_1  |     access_log off;
web_1  |     error_log off;
web_1  |
web_1  |     location / {
web_1  |         try_files $uri $uri/ /index.php$is_args$args;
web_1  |     }
web_1  |
web_1  |     location ~ \.php$ {
web_1  |         fastcgi_pass php:9000; #<- ちゃんとここが php になってる
web_1  |         fastcgi_index index.php;
web_1  |         fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
web_1  |         include       fastcgi_params;
web_1  |     }
web_1  | }
web_1  |

この方法が優れているとは思えませんが、とりあえずやりたいことは達成できたので満足です。

あらため、たったひとつの全く優れている方法

ブコメでご指摘いただいた結果、 ${DOLLAR} なんてどう考えてもイケてない対応は要らないことがわかりました。ご指摘ありがとうございました :bow:

あらためて envsubst のヘルプを見ますと以下のように書いてあります

使用法: envsubst [オプション] [シェル形式]
シェル形式が指定されると, シェル形式で参照される, そのような環境変数だけが
代入されますが, そうでない場合は標準入力に現れる全ての環境変数が代入されます.

この部分を完全にスルーしてたのですが、ようは envsubst '$PHP_APP_HOST' のような書き方にすることで、今回の目的でいうところの fastcgi_pass ${PHP_APP_HOST}:9000 の部分だけが置換され、他の $ は無視される(そのまま残る)ことになります。

やってみます。

default.conf.template

fastcgi_pass のとこだけが ${PHP_APP_HOST} になってますが、他( $document_root など)はそのままです。直感的なもともと書きたかった記法どおりです。

server {
    listen 80 default;
    server_name _;
    root /var/www/html;
    index index.php index.html index.htm;
    charset utf-8;

    access_log off;
    error_log off;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass ${PHP_APP_HOST}:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

docker-compose.ymlの command 部分

DOLLAR='$$' なんていう醜悪なものは消え、 envsubst '$$PHP_APP_HOST' の指定になりました。

command: /bin/sh -c "envsubst '$$PHP_APP_HOST' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

結果

web_1  | server {
web_1  |     listen 80 default;
web_1  |     server_name _;
web_1  |     root /var/www/html;
web_1  |     index index.php index.html index.htm;
web_1  |     charset utf-8;
web_1  |
web_1  |     access_log off;
web_1  |     error_log off;
web_1  |
web_1  |     location / {
web_1  |         try_files $uri $uri/ /index.php$is_args$args;
web_1  |     }
web_1  |
web_1  |     location ~ \.php$ {
web_1  |         fastcgi_pass php:9000;
web_1  |         fastcgi_index index.php;
web_1  |         fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
web_1  |         include       fastcgi_params;
web_1  |     }

大勝利です :ok_woman:

また、複数の変数をセットしたい場合は、以下のように envsubst のシェル形式の部分に複数追加すれば良いようです。

たとえば conf.template が

fastcgi_pass ${PHP_APP_HOST}:${PHP_APP_PORT};

のような形式の場合、 docker-compose.yml

environment:
 - PHP_APP_HOST=php
 - PHP_APP_PORT=9000
command: /bin/sh -c "envsubst '$$PHP_APP_HOST$$PHP_APP_PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

のように $$PHP_APP_HOST$$PHP_APP_PORT とすることで、

fastcgi_pass php:9000;

のように、ただしくenvから値を複数設定することができました :ok_hand:

教えていただいて本当にありがとうございました!!!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away