#はじめに
ローカル環境でrailsを使用し開発を進めていましたが、今後のデプロイを考慮し、環境をDockerによるコンテナで管理することとしました。
初めてでかなり時間がかかってしまったため、備忘録として残します。
同様の環境構築が必要な方の参考になれば幸いです。
#環境構築にあたっての目標
- ローカルの環境をコンテナ化し、本番環境構築時に容易にしたい。
- Dockerのイメージは、効率化を考慮し、できるだけ軽量化したい。
- bundleやyarnのモジュールを永続化することにより、起動にかかる時間を減らしておきたい。
- DBはsqliteを使用していたので、ついでにMysqlにしたい。
#環境
###コンテナの構成
- db
- web
- webpacker←webpack_dev_server実行用
###バージョン
- ruby 2.7.2
- Rails 6.0.3
- Mysql 8.0.22
##一覧
今回変更した箇所のみ記載しています。
ルートディレクトリはrailsアプリのルートディレクトリとしています。
.
├── config
│ ├── database.yml #更新
│ └── webpacker.yml #更新
├── docker
│ └── rails
│ └── Dockerfile #新規作成
├── docker-compose.yml #新規作成
├── .env #新規作成
└── Gemfile #更新
##Dockerfile
#軽量化のため、alpineを使用。
FROM ruby:2.7.2-alpine3.12
ENV TZ="Asia/Tokyo" \
LANG="C.UTF-8" \
APP_ROOT="/app" \
ENTRYKIT_VERSION="0.4.0"
WORKDIR $APP_ROOT
#ENTRY KITの導入
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& mv entrykit /bin/entrykit \
&& chmod +x /bin/entrykit \
&& entrykit --symlink
RUN apk update \
&& apk add --no-cache \
alpine-sdk \
bash \
build-base \
mysql-client \
mysql-dev \
nodejs \
tzdata \
yarn
COPY . $APP_ROOT
ENTRYPOINT [ \
"prehook", "bundle install -j4 --quiet", "--", \
"prehook", "yarn install --check-files --ignore-optional", "--"]
###イメージの軽量化
今後の作業の効率化のため、イメージの軽量化するために、alpineを使用しています。
約1.6GB→約400MBまで容量が減りました。
マルチステージビルドを使用し、nodejsのインストールを別にすることで、さらにイメージの軽量化ができそうです。
ある程度軽量化できたので、今回はここまでとしました。
###ENTRY KITの導入
ENTRY KITを使用し、コンテナ起動時にyarn install
、bundle install
を実行するようにしています。
調べた記事では、Dockerfile内で実行しているものが多かったですが、以下の問題があるため今回は見送りました。
- gem等の追加が必要となった時は、コンテナのbuildからやり直す必要がある。
- コンテナをdownすると、installしたgem等が保持されない。
そのためコンテナ起動時の実行かつ、モジュールを永続化することで、上記の問題を解決する構成としました。
##docker-compose.yml
version: '3'
services:
db:
image: mysql:8.0.22
command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
ports:
- '3306:3306'
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_DATABASE: ${DATABASE}
MYSQL_ROOT_PASSWORD: ${ROOTPASS}
MYSQL_USER: ${USERNAME}
MYSQL_PASSWORD: ${USERPASS}
web: &app_base
build:
context: .
dockerfile: ./docker/rails/Dockerfile
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b 0.0.0.0"
depends_on:
- db
ports:
- "3000:3000"
volumes:
- .:/app
- gem_modules:/app/vendor/bundle
- node_modules:/app/node_modules
tty: true #binding.pry実行用
stdin_open: true #binding.pry実行用
environment:
WEBPACKER_DEV_SERVER_HOST: webpacker #webpack_dev_server実行用のコンテナを指定
BUNDLE_APP_CONFIG: ./.bundle
NODE_ENV: development
RAILS_ENV: development
webpacker: #webpack_dev_server実行用のコンテナ
<<: *app_base
command: bash -c "bundle exec bin/webpack-dev-server"
depends_on:
- web
ports:
- '3035:3035'
tty: false #binding.pry不要なのでfalseへ変更
stdin_open: false #binding.pry不要なのでfalseへ変更
environment:
BUNDLE_APP_CONFIG: ./.bundle
WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
NODE_ENV: development
RAILS_ENV: development
volumes:
db_data:
gem_modules:
node_modules:
- DBのユーザ情報は別ファイルとしておきたかったので、変数化し
.env
に記載しています。 - コンテナをdownさせても、DB、モジュールのデータが失われないよう、永続化しています。
##config/database.yml
default: &default
- adapter: sqlite3
+ adapter: mysql2
+ username: app
+ password: password
+ host: db #サービス名を指定
development:
<<: *default
- database: db/development.sqlite3
+ database: mysql_development
##config/webpacker.yml
default: &default
dev_server:
- host: localhost
+ host: webpack #サービス名を指定
##Gemfile
-gem 'sqlite3', '~> 1.4'
+gem 'mysql2', '0.5.3'
##.env
DATABASE=mysql_development
USERNAME=app
USERPASS=password
ROOTPASS=password
##疑問点・解消できなかったこと
###コンテナ初回起動時エラー(stack Error: getaddrinfo EAI_AGAIN nodejs.org)
コンテナ初回起動時のyarn install
実行中に、エラーが発生することがあります。
調べた結果、原因はDNSで名前解決できていないことだそう。
解決方法と思われるホスト側のDNS変更や、docker-compose.yml
でのDNSの指定等試しましたが、解決しませんでした。
2回目起動時はエラーが発生せず、コンテナの再起動を解決方法としている記事もあったため、このままとしておきます。
error /app/node_modules/node-sass: Command failed.
web_1 | Exit code: 1
web_1 | Command: node scripts/build.js
web_1 | Arguments:
web_1 | Directory: /app/node_modules/node-sass
web_1 | Output:
web_1 | Building: /usr/bin/node /app/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
web_1 | gyp info it worked if it ends with ok
web_1 | gyp verb cli [
web_1 | gyp verb cli '/usr/bin/node',
web_1 | gyp verb cli '/app/node_modules/node-gyp/bin/node-gyp.js',
web_1 | gyp verb cli 'rebuild',
web_1 | gyp verb cli '--verbose',
web_1 | gyp verb cli '--libsass_ext=',
web_1 | gyp verb cli '--libsass_cflags=',
web_1 | gyp verb cli '--libsass_ldflags=',
web_1 | gyp verb cli '--libsass_library='
web_1 | gyp verb cli ]
web_1 | gyp info using node-gyp@3.8.0
web_1 | gyp info using node@12.20.1 | linux | x64
web_1 | gyp verb command rebuild []
web_1 | gyp verb command clean []
web_1 | gyp verb clean removing "build" directory
web_1 | gyp verb command configure []
web_1 | gyp verb check python checking for Python executable "python2" in the PATH
web_1 | gyp verb `which` succeeded python2 /usr/bin/python2
web_1 | gyp verb check python version `/usr/bin/python2 -c "import sys; print "2.7.18
web_1 | gyp verb check python version .%s.%s" % sys.version_info[:3];"` returned: %j
web_1 | gyp verb get node dir no --target version specified, falling back to host node version: 12.20.1
web_1 | gyp verb command install [ '12.20.1' ]
web_1 | gyp verb install input version string "12.20.1"
web_1 | gyp verb install installing version: 12.20.1
web_1 | gyp verb install --ensure was passed, so won't reinstall if already installed
web_1 | gyp verb install version not already installed, continuing with install 12.20.1
web_1 | gyp verb ensuring nodedir is created /root/.node-gyp/12.20.1
web_1 | gyp verb created nodedir /root/.node-gyp
web_1 | gyp http GET https://nodejs.org/download/release/v12.20.1/node-v12.20.1-headers.tar.gz
web_1 | gyp WARN install got an error, rolling back install
web_1 | gyp verb command remove [ '12.20.1' ]
web_1 | gyp verb remove using node-gyp dir: /root/.node-gyp
web_1 | gyp verb remove removing target version: 12.20.1
web_1 | gyp verb remove removing development files for version: 12.20.1
web_1 | gyp ERR! configure error
web_1 | gyp ERR! stack Error: getaddrinfo EAI_AGAIN nodejs.org
web_1 | gyp ERR! stack at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)
web_1 | gyp ERR! System Linux 4.19.121-linuxkit
web_1 | gyp ERR! command "/usr/bin/node" "/app/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
web_1 | gyp ERR! cwd /app/node_modules/node-sass
web_1 | gyp ERR! node -v v12.20.1
web_1 | gyp ERR! node-gyp -v v3.8.0
web_1 | gyp ERR! not ok
###マルチステージビルドでのイメージの軽量化
時間短縮等、更なる効率化が必要となった時に試してみようと思います。
#最後に
新規のRailsアプリをDockerで環境構築する記事はあったのですが、既存のRailsアプリをコンテナ化する記事はあまりなかったので苦労しました。
開発環境として一旦構築したので、テスト環境や本番環境の分け方・構築も検討して取り組んでいきます。
他にも以下の内容も取り組んでいきたいので、実施できれば記事にしようかと思います。
- webサーバとしてnginxの導入
- CI/CDの導入
- AWS上での環境構築
###1/26追記
このままではRspec×Capybara×ChromeDriverが正常に動かなかったので別の記事を書きました。。。
・Docker環境でRspec×Capybara×ChromeDriverを動作させる - Qiita
#参考にさせて頂いた記事
・開発しやすいRails on Docker環境の作り方 - Qiita
・Rails newからproductionモードで動くようになるまで - Qiita
・Rails 6.0 × MySQL8でDocker環境構築(Alpineベース) - Qiita
・Docker Composeのvolumesを使ってもっと効率的に - Qiita
・【Dockerfile全解説】Rails本番環境のための一番シンプルなDockerイメージを作る - Qiita
・【Docker】Rails開発で知っておきたい!gemの永続化による作業効率アップの話 | Enjoy IT Life
・docker-composeでの環境構築で留意しておきたいところ - Qiita
・Rails + webpacker on Dockerの環境をdocker-composeで構築する - RoadMovie