LoginSignup
187
179

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-09-01

フロントとバックエンドを同一コンテナで済ませるパターンと, 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

参考

187
179
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
187
179