シンプルな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とか他のサーバと合わせて動かすとどうなるかちょっと分かってない。

Dockerの勉強がてら、HerokuのデプロイをDocker化(という言葉であってるのか?)した。

参考: Building Docker Images with heroku.yml | Heroku Dev Center
作業内容はこちら: Heroku container deploy by okonomi · Pull Request #152 · okonomi/blg

まずstackをcontainerに変更する。
herokuコマンドで切り替え:

heroku stack:set container -a oknmjp
Review Appはapp.jsonの指定を変更:

{
   "stack": "container"
}
次に、アプリの定義ファイルheroku.ymlを作る:

build:
  docker:
    web: Dockerfile.production
  config:
    GITHUB_OAUTH_CLIENT_KEY: xxx
    GITHUB_OAUTH_CLIENT_SECRET: xxx
    SECRET_KEY_BASE: xxx
release:
  image: web
  command:
    - bin/rails db:prepare
run:
  web: bin/rails server -p $PORT
あとはDockerfile.productionをいい感じに用意する。

ハマったこと:
  • イメージビルド時にbin/rails assets:precompileを実行しているが、SECRET_KEY_BASEを設定する必要があった
  • Railsサーバの起動ポートは$PORTで指定しないといけない
解決してないこと:
  • ビルド時にSECRET_KEY_BASEとかの漏洩しちゃいけない値はどうDockerに渡せばいいのか
    • アプリのConfig Varsはビルド時には参照できない
    • いまはとりあえずダミー値を渡してるけどあんまり良くない気がする
まだDocker化した恩恵は感じてないけど…

Railsサーバとwebpack-dev-serverを別コンテナで起動して、WEBPACKER_DEV_SERVER_HOSTでRailsにwebpack-dev-serverの居場所を教えてあげる形にした。

version: "3.7"

services:
  web: &app_base
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - bundle:/app/.bundle
      - node_modules:/app/node_modules
      - home:/root
    environment:
      BUNDLE_PATH: /app/.bundle
      BUNDLE_JOBS: 4
      WEBPACKER_DEV_SERVER_HOST: webpacker
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
  webpacker:
    <<: *app_base
    ports:
      - "3035:3035"
    command: bin/webpack-dev-server
  db:
    image: postgres:11.2
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/data
    environment:
      PGDATA: /data

volumes:
  bundle:
  node_modules:
  pg_data:
  home: