Railsアプリを作るときにいつもやってることをメモしておく。

まずディレクトリ作ってそこに入って:
$ mkdir awesome-rails-app
$ cd awesome-rails-app
Gemfile作って:
$ bundle init
RailsをGemfileに追加:
$ bundle add rails --skip-install
Railsをvendor/bundle以下にインストール:
$ bundle install --path=vendor/bundle -j4
vendor/bundleにインストールしたRailsを使ってrails new実行(-fオプションでファイル上書き):
$ bundle exec rails new . -f
/vendor/bundleをGit管理から除外:
echo /vendor/bundle >> .gitignore

おわり。

Railsをグローバルにインストールしたくないのでこんなことしてる。Railsの依存でいろんなgemが入るのが嫌なので…。

シンプルなRailsアプリを作って、それをDockerで動かすためのDockerfileを書いてみた。
こんなかんじ:

FROM ruby:2.6.3-alpine3.9 AS builder

ENV RAILS_ENV production

WORKDIR /app

RUN apk add --update --no-cache \
    build-base \
    libxml2-dev \
    libxslt-dev \
    sqlite-dev \
    nodejs \
    yarn \
    tzdata

# install gems
RUN gem install bundler
COPY Gemfile .
COPY Gemfile.lock .
RUN bundle install --clean --frozen --jobs $(nproc) --without development test

# install npm packages
COPY package.json .
COPY yarn.lock .
RUN yarn install --frozen-lockfile

# compile assets
COPY Rakefile .
COPY bin bin
COPY .browserslistrc .
COPY postcss.config.js .
COPY babel.config.js .
COPY config config
COPY app/assets app/assets
COPY app/javascript app/javascript
RUN bin/rails assets:precompile


FROM ruby:2.6.3-alpine3.9

ENV RAILS_ENV production
ENV RAILS_LOG_TO_STDOUT 1
ENV RAILS_SERVE_STATIC_FILES 1

WORKDIR /app

RUN apk add --update --no-cache \
    sqlite-libs \
    tzdata

COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY --from=builder /app/public/assets /app/public/assets
COPY --from=builder /app/public/packs /app/public/packs
COPY . .

RUN bin/rails db:schema:load

EXPOSE 3000
ENTRYPOINT ["bin/rails"]

あと.dockerignoreはこうした:

.bundle
.dockerignore
.git
.gitignore
Dockerfile
log
node_modules
public/assets
public/packs
README.md
storage
test
tmp

  • マルチステージビルドを使って、ビルドステージではgemのインストールとassets:precompileを行う。
  • 次のステージでRailsサーバを動かすためだけの最小限のファイルをコピーする。
これでイメージサイズが160Mくらい。もう少し小さくできそうな気もする。
あとDBとか他のサーバと合わせて動かすとどうなるかちょっと分かってない。

Railsアプリを本番デプロイしたらsecret_key_baseが未定義っておこられたので、どういう扱いになってるのか確認してみた。
確認したRailsのバージョンは6.0.0.rc1。

はじめにまとめ


secret_key_baseの設定場所は4箇所ある:
  1. 環境変数: ENV["SECRET_KEY_BASE"]
  2. credencials: config/credentials.yml.enc
  3. secrets: config/secrets.yml (Encrypted secretsが有効ならconfig/secrets.yml.enc
  4. tmp/development_secret.txt
環境ごとの評価順:
  1. developmentかtestなら、secrets、tmp/development_secret.txtの順
  2. それ以外の環境なら、環境変数、credencials、secretsの順

secret_key_baseの取得

 Rails.applicatin.secret_key_base
#=> "bb7a320962dda4262e21262f80229946f5a3b3c05cc8968d71d1ae9e40c64b5d7afb5340c196134089590b69d8a51a05ee801217b90eb859236bf4b14f8d5a76"

secret_key_baseの定義場所

Rails::Applicationにメソッドとして定義されている。

railties/lib/rails/application.rb
 module Rails
   # snip
 
   class Application < Engine
     # snip
 
     def secret_key_base
       if Rails.env.development? || Rails.env.test?
         secrets.secret_key_base ||= generate_development_secret
       else
         validate_secret_key_base(
           ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
         )
       end
     end
   end
 end
  • 環境がdevelopmentかtestの場合
    • secrets.secret_key_baseが未設定なら、generate_development_secretを実行し結果をsecrets.secret_key_baseに保存する
    • secrets.secret_key_baseを返す
  • それ以外の場合
    • ENV["SECRET_KEY_BASE"]credentials.secret_key_basesecrets.secret_key_baseの順番で評価してバリデーションした上で結果を返す

secrets.secret_key_baseとは

Rails::Application#secretsが定義されてる。

railties/lib/rails/application.rb
     def secrets
       @secrets ||= begin
         secrets = ActiveSupport::OrderedOptions.new
         files = config.paths["config/secrets"].existent
         files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets
         secrets.merge! Rails::Secrets.parse(files, env: Rails.env)
 
         # Fallback to config.secret_key_base if secrets.secret_key_base isn't set
         secrets.secret_key_base ||= config.secret_key_base
 
         secrets
       end
     end
  • config/secrets.ymlを読み込む
  • Rails 5.1で導入されたEncrypted secretsが有効ならconfig/secrets.yml.encを読み込む
  • config/secrets.ymlにsecret_key_baseが未定義ならconfigから読み込む
config/secrets.ymlはRails 4.1で導入されたようなので、それ以前はconfig/application.rbとかに書いてたのかな?
https://guides.rubyonrails.org/4_1_release_notes.html#config-secrets-yml

generate_development_secretとは

railties/lib/rails/application.rb
       def generate_development_secret
         if secrets.secret_key_base.nil?
           key_file = Rails.root.join("tmp/development_secret.txt")
 
           if !File.exist?(key_file)
             random_key = SecureRandom.hex(64)
             FileUtils.mkdir_p(key_file.dirname)
             File.binwrite(key_file, random_key)
           end
 
           secrets.secret_key_base = File.binread(key_file)
         end
 
         secrets.secret_key_base
       end
 secret_key_baseが未設定ならtmp/development_secret.txtを自動生成してその内容を返すらしい。へー。

まとめ

  •  開発中はsecret_key_baseが自動生成されるので未設定で良さそう
  •  本番環境は環境変数、credencials、secretsなど適切な方法で設定しておく