docker-compose+Nginx+Rails6 で静的コンテンツをNginxから配信する
なかなか自分のわかってないところにピタリな情報がなくて辛い気持ちになりながら戦いました。
TL;DR
- docker-compose.yml で rails 側の
public
を/app/public
とかにバインディングしてやる - Nginx の 設定ファイル
/etc/nginx/conf.d/default.conf
で-
root /app/public;
としてやる -
try_files $uri @puma;
としてやる(要upstream puma {}
)
-
- これで public 配下でマッチングしたものはそれを返す、マッチングしなかったものは @puma にリバースプロキシする、ができる
config.public_file_server.enabled = true
Rails6 のよくある設定としては
/config/enviroments/development.rb
では
config.public_file_server.enabled = true
で、/config/enviroments/production.rb
では
config.public_file_server.enabled = false
とするとで開発環境はpuma(rails側のサーバ)から静的ファイルを返し
本番環境はそれをやらない、という設定。
なので、Webで本番環境での静的ファイル配信の情報を探そうとしても
/config/enviroments/production.rb
で
config.public_file_server.enabled = true
としてやればOKでした!
という情報が結構出てくる。
違う、そうじゃない(画像略)
たぶんホットリロードとかそういう部分の関係なんだろうけど
開発環境と本番環境で構成が違うっていうの個人的には嫌いなんですよね。
それもあって Docker(docker-compose.yml) 使ってるのに
なんで開発環境と本番環境で構成が違うのをやらないといけないんだァ〜!
ということで
/config/enviroments/development.rb
でも
config.public_file_server.enabled = false
とした状態でやれる方法を探していました。
rails assets:precompile
当初、どうやって public
配下と app/assets/
配下のルーティングを切り分けるんだ、
と思って闇雲にWebを探し回っていたんですが
これは webpacker の挙動をちゃんと理解してませんでしたね。
rails assets:precompile
とやってやることで public ディレクトリ配下に webpacker の結果が出力されるから
Nginx は public ディレクトリ配下(とアプリケーションへのリバースプロキシ)だけを監視してやれば
よかったんでした。
Nginx のルーティング設定
結論から言えば /etc/nginx/conf.d/default.conf をよくわかってないのが
「やれる方法を探」すハメになった原因でした。
location ディレクティブ
- nginx連載5回目: nginxの設定、その3 | locationディレクティブ | Powered by HEARTBEATS
からの引用。
# URIのパスに対するファイル(静的コンテンツ)が存在すれば、そのファイル返す。
# 存在しなければ、動的コンテンツとして@webappに内部リダイレクトする。
location / {
try_files $uri @webapp;
}
あーーーなるほど! try_files
は順次解釈して適用してくれるんですね!
めっちゃ賢いじゃないか!
ということは
docker-compose.yml
でこうして
web:
image: nginx:1.18
container_name: web
ports:
- "8080:80"
volumes:
- ./forDocker/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./public:/app/public
depends_on:
- app
※docker-compose.yml は
rails アプリディレクトリ直下にあるとする。public は rails アプリにある public ディレクトリ。
/etc/nginx/conf.d/default.conf
をこうして
upstream puma {
server app:3000;
}
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /app/public;
location @puma {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass http://puma;
}
# URIのパスに対するファイル(静的コンテンツ)が存在すれば、そのファイル返す。
# 存在しなければ、動的コンテンツとして@pumaに内部リダイレクトする。
location / {
try_files $uri @puma;
}
location ~ ^/(assets|packs)/ {
gzip_static on;
brotli_static on;
expires max;
add_header Cache-Control public;
}
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /500.html {
}
}
- 以下も参考
- webpacker/docs/deployment.md | GitHub
これで以下が実現され、
config.public_file_server.enabled = false
の状態に置いても docker-compose+Nginx+Rails6 で
静的コンテンツをNginxから配信することが実現できました。
- Rails6 の
public
ディレクトリがNginx 側のapp/public
ディレクトリにバインド -
public
ディレクトリにはrails assets:precompile
でトランスパイル後のファイルが入っている -
/etc/nginx/conf.d/default.conf
の-
upstream puma
でリバースプロキシの設定 -
location / { try_files $uri @puma;}
でroot /app/public;
にあればそれを返し、なければ puma にリバースプロキシ
-
docker-compose.yml で出てくる謎の depends_on:
などは
そのうち Nginx+Rails6+MySQL な docker-compose.yml の記事をあげます。
その他の参考リンク
- Rails newからproductionモードで動くようになるまで | Qiita
- 丁寧にNginx + MySql + Rails6 (Using Webpacker) on Dockerな開発環境を構築します。 | Qiita
Discussion
当記事を参考にnginxの設定を行なっていますが、404エラーのときにnginxの404画面へリダイレクトしてしまいます。
もしよろしければnginx用のDockerfileの設定を教えていただけると嬉しいです。
よろしくお願いします!
そうなるように設定しています。
Rails 側(puma)で処理したいのであれば、上記記事の以下のあたりを消してみるといかがでしょうか