Nginx + Rails (Puma) on Docker のいくつかの実用パターン

  • 62
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

フロントとバックエンドを同一コンテナで済ませるパターンと, 1 プロセス 1 コンテナの原則に従い個別コンテナでやるパターンです.

他にも, 共有ストレージ専用の busybox を利用するパターン などがあるようです.

今回の要件

  • フロントエンドは Nginx, バックエンドは Rails5 (Puma)
  • Nginx-Puma 間は socket 通信
  • Rails の静的コンテンツ (public/ 配下) は Nginx が処理する

パターンA: Nginx と Rails が同一コンテナ

全てのコードはこちらにあります.
https://github.com/na-o-ys/rails_docker_sample/tree/single-container

全体図

$ rails new 直後のディレクトリに Dockerfile などを配置したもの.

一つの Dockerfile で Rails のセットアップ, Nginx のインストール & セットアップを行うため, 複数コンテナの連携など特別なことをする必要は無い.

.
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── app/
├── config
│   ├── ...
│   └── puma.rb
├── log
├── nginx.conf
├── public
│   ├── 404.html
│   └── ...
└── .dockerignore

Puma

最終行で, #{app_root}/tmp/sockets/puma.sock に bind しています.

config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

Nginx

  • /app/public を静的コンテンツとして配信
  • その他を /app/tmp/sockets/puma.sock にプロキシ
nginx.conf
# https://github.com/puma/puma/blob/master/docs/nginx.md
upstream app {
  server unix:///app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name localhost; # TODO

  keepalive_timeout 5;

  # static files
  root /app/public;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;

    # static files
    if (-f $request_filename) {
      break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }

    if (!-f $request_filename) {
      proxy_pass http://app;
      break;
    }
  }

  location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
    expires max;
    break;
  }
}

Dockerfile

ホストのカレントディレクトリ (Rails root) を /app にマウントしています.

FROM ruby:2.3.1
RUN apt-get update -qq && \
    apt-get install -y build-essential libpq-dev nodejs && \
    apt-get install -y nginx

# Nginx
ADD nginx.conf /etc/nginx/sites-available/app.conf
RUN rm -f /etc/nginx/sites-enabled/default && \
    ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/app.conf

# Rails App
RUN mkdir /app
WORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install
ADD . /app
RUN mkdir /app/tmp/sockets

# Start Server
CMD bundle exec puma -d && \
    /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

.dockerignore

ホスト側のゴミファイルが紛れ込まないように.

tmp/*
log/*
.git

パターンB: Nginx と Rails が個別コンテナ

全体図

全てのコードはこちらにあります.
https://github.com/na-o-ys/rails_docker_sample/tree/multiple-container

$ rails new 直後のディレクトリに Dockerfile などを配置したもの.

Rails 用の Dockerfile (ルート直下) と Nginx 用の Dockerfile (containers/nginx/Dockerfile) を作成し, Docker Compose で2つのコンテナをまとめて起動します.

.
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── app/
├── config
│   ├── ...
│   └── puma.rb
├── containers
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
├── docker-compose.yml
├── log/
├── public
│   ├── 404.html
│   └── ...
└── .dockerignore

.dockerignore

containers/ を ignore する.

tmp/*
log/*
.git
containers

Puma

ファイルはパターンAと全く同じです. 最終行で #{app_root}/tmp/sockets/puma.sock に bind しているのがポイント.

config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

Dockerfile (Rails)

VOLUME を使って, /app/public/app/tmp を他コンテナからマウント可能にしています. それぞれ静的コンテンツと socket を Nginx コンテナから利用するためです.

FROM ruby:2.3.1
RUN apt-get update -qq && \
    apt-get install -y build-essential libpq-dev nodejs

# Rails App
RUN mkdir /app
WORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install
ADD . /app
RUN mkdir -p tmp/sockets

# Expose volumes to frontend
VOLUME /app/public
VOLUME /app/tmp

# Start Server
# TODO: environment
CMD bundle exec puma

Nginx

パターンAと全く同じです.

  • /app/public を静的コンテンツとして配信
  • その他を /app/tmp/sockets/puma.sock にプロキシ
nginx.conf
# https://github.com/puma/puma/blob/master/docs/nginx.md
upstream app {
  server unix:///app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name localhost; # TODO

  keepalive_timeout 5;

  # static files
  root /app/public;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;

    # static files
    if (-f $request_filename) {
      break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }

    if (!-f $request_filename) {
      proxy_pass http://app;
      break;
    }
  }

  location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
    expires max;
    break;
  }
}

Dockerfile (Nginx)

nginx のイメージを使います. /etc/nginx/sites-enabled/ がデフォルトで認識されないので, /etc/nginx/conf.d/ にコンフィグを配置しています.

FROM nginx:1.11.3
RUN rm -f /etc/nginx/conf.d/*
ADD nginx.conf /etc/nginx/conf.d/app.conf
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

Docker Compose

ポイントは nginxvolumes_from: web です. これにより, web コンテナの /app/public/app/tmpnginx コンテナにマウントされ, 静的コンテンツ配信と socket 通信が可能になります.

docker-compose.yml
version: '2'
services:
  web:
    build: .
    volumes:
      - /app/log # persist logs
    depends_on:
      - db

  nginx:
    build: containers/nginx
    ports:
      - "80:80"
    volumes_from:
      - web
    depends_on:
      - web

  db:
    image: postgres

参考