Lambdaカクテル

京都在住Webエンジニアの日記です

DockerでNginx動かしてたら全然locationディレクティブが効かない件とその解決

2時間くらいウンウン唸るはめになったのでメモ。

tl;dr

  • DockerでNginxを使う場合はdefault.confを無視するな
  • 大抵の場合において不要なのでincludeディレクティブを削除しろ

シチュエーション

TypeScript開発のために,Dockerのnginxオフィシャルイメージでファイル(index.htmlとかentrypoint.js)をホスティングしたい。

nginxの設定は以下の通りにして,docker-composeを用いてコンテナを起動した。

# ...snip...
http {
   # ... snip ...
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;

        location / {
            root /usr/share/nginx/html;
        }

        location ~ ^/js/(.+)\.js$ {
            root /usr/share/nginx/;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

    }
}

すなわち,JSファイルだけ別ディレクトリから読み込みたい,という具合だ(ホストの側で別々のディレクトリで管理し,Dockerコンテナにマウントしているという事情がある)。

症状

全くこのlocation設定が効いていない。index.htmlは普通に配信されているが,/js/entrypoint.jsをGETしようとすると404が返ってきてしまう……。

正規表現がおかしいのかな?などと勘ぐって調整してみたが全く変化がない。どういうこと??

答え

この状況では,コンテナの80番ポートでnginxがリッスンし,これにホストの8000番ポートをバインドしていた。

version: '3'
services:
    nginx:
        image: nginx:1.19.0
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
            - ./dist/:/usr/share/nginx/js/:ro
            - ./html/:/usr/share/nginx/html/:ro
        ports: 
            - 8000:80

するとhostnameは当然localhost:8000としてnginxに送られてくる。しかしながら8000番に対応するserverディレクティブは存在しないので,デフォルトの設定にフォールバックしてしまい,全てのファイルをデフォルトのファイルパスから読むような動作になっていた。

Q. でもdefault_serverを指定していたんじゃないの?

/etc/nginx/conf.d/default.confにはこう書かれている(コメントは削除している):

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

server_name ディレクティブには localhostが指定されているため,優先的にこちらがヒットしてしまったのだった。

どのようにしてリクエストが処理されるか,ドキュメントを見てみよう。

nginx.org

In this configuration nginx tests only the request’s header field “Host” to determine which server the request should be routed to. If its value does not match any server name, or the request does not contain this header field at all, then nginx will route the request to the default server for this port.

つまり,default_serverを必要とするまでもなく,server_nameが合致したのでこちらが選ばれた,というわけ。

対処

httpディレクティブで指定されていたinclude /etc/nginx/conf.d/*.conf;を削除するとあっさり直った。

defaultなんだし放置で良いでしょって思ってたら痛い目を見たという話でした。

まとめ

デフォルトが用意されているアプリケーションでは,デフォルト値の吸引力が強い場合があるので気を付けよう!

追記

docker-compose run --rm nginx nginx -Tみたいに実行すると,有効になっているコンフィグが全てstdoutに掃き出されるので活用しよう。

thanks to: id:nabeop