🛰️

docker-compose+Nginx+Rails6 で静的コンテンツをNginxから配信する

2020/11/23に公開2

なかなか自分のわかってないところにピタリな情報がなくて辛い気持ちになりながら戦いました。

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 ディレクティブ

からの引用。

# 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 {
    }
}

これで以下が実現され、

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 の記事をあげます。

その他の参考リンク

Discussion

fsefasfsefas

当記事を参考にnginxの設定を行なっていますが、404エラーのときにnginxの404画面へリダイレクトしてしまいます。
もしよろしければnginx用のDockerfileの設定を教えていただけると嬉しいです。
よろしくお願いします!

北山淳也北山淳也

404エラーのときにnginxの404画面へリダイレクトしてしまいます

そうなるように設定しています。
Rails 側(puma)で処理したいのであれば、上記記事の以下のあたりを消してみるといかがでしょうか

-    error_page 404 /404.html;
-    location = /404.html {
-    }

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