dockerでrailsを動かすときの構成はどうするべきか
はじめに
dockerでrailsを動かす場合にどうするのが良いかなーと試行錯誤し、構成も落ち着いてきたのでまとめます。
お試しバージョン
一番最初はとりあえずってことで、railsリポジトリ + railsを動作させるコンテナの組み合わせで試してみました。
Dockerfileの内容
FROM base
# rubyインストールに必要なパッケージを用意
RUN apt-get update
RUN apt-get install -y --force-yes build-essential curl git zlib1g-dev libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt-dev
# rbenv, ruby-buildをインストール
RUN git clone https://github.com/sstephenson/rbenv.git /root/.rbenv
RUN git clone https://github.com/sstephenson/ruby-build.git /root/.rbenv/plugins/ruby-build
RUN ./root/.rbenv/plugins/ruby-build/install.sh
ENV PATH /root/.rbenv/bin:$PATH
RUN echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh # or /etc/profile
RUN echo 'eval "$(rbenv init -)"' >> .bashrc
# rubyインストール(バージョン指定は ruby-versions.txt に)
ENV CONFIGURE_OPTS --disable-install-doc
ADD ./ruby-versions.txt /root/ruby-versions.txt
RUN xargs -L 1 rbenv install < /root/ruby-versions.txt
# bundlerを各rubyに用意
RUN echo 'gem: --no-rdoc --no-ri' >> /.gemrc
RUN bash -l -c 'for v in $(cat /root/ruby-versions.txt); do rbenv global $v; gem install bundler; done'
# sqlite3をインストール
RUN apt-get install -y sqlite3 libsqlite3-dev
# Node.jsインストール
RUN apt-get install -y software-properties-common
RUN apt-get update
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN echo "deb http://archive.ubuntu.com/ubuntu precise universe" >> /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y nodejs
# ssh準備
RUN mkdir /root/.ssh/
# private keyをコピー
ADD id_rsa /root/.ssh/id_rsa
# known_hosts設定
RUN touch /root/.ssh/known_hosts
# Add bitbuckets key
RUN ssh-keyscan bitbucket.org >> /root/.ssh/known_hosts
# railsアプリケーションをclone
RUN mkdir -p /var/www
RUN git clone git@github.com:k-shogo/rails4-ajax-sample.git /var/www/rails
WORKDIR /var/www/rails
# railsのセットアップ
RUN bash -l -c 'bundle install'
RUN bash -l -c 'bundle exec rake db:create; bundle exec rake db:migrate'
RUN bash -l -c 'bundle exec rake master:import'
ADD ./secrets.yml /var/www/rails/config/secrets.yml
EXPOSE 3000
CMD bash -l -c 'bundle exec rails s'
イケてないポイント
動作はしますが、docker build
するたびにrubyからビルドし直すのは当然イケてないので早々に止めました
試行錯誤バージョン
毎回rubyをビルドし直すのが時間がかかるので、rubyが入ったコンテナのリポジトリを分離し、ruby, node等を用意したコンテナ + railsリポジトリ + railsが動作するコンテナ の3リポジトリ構成にしてみました。
つまり先ほどのDockerfileを半分に分割する感じです。
さらにrailsのセットアップ部分でbash -l -c
とかつけるのもイケてないと感じたので、セットアップのスクリプトを分離しても見ました。
railsを動作させるコンテナのDockerfile
FROM kshogo/docker-rails-base
# Make ssh dir
RUN mkdir /root/.ssh/
# Copy over private key, and set permissions
ADD id_rsa /root/.ssh/id_rsa
# Create known_hosts & Add bitbuckets key
RUN \
touch /root/.ssh/known_hosts && \
ssh-keyscan bitbucket.org >> /root/.ssh/known_hosts
# install rails application
ADD ./setup.sh /root/setup.sh
RUN chmod +x /root/setup.sh
RUN /root/setup.sh
# Add configuration files in repository to filesystem
ADD ./rails/config/secrets.yml /var/www/rails/config/secrets.yml
ADD ./start-server.sh /usr/bin/start-server
RUN chmod +x /usr/bin/start-server
WORKDIR /var/www/rails
EXPOSE 3000
# ENTRYPOINT ["/usr/bin/start-server"]
CMD ["/usr/bin/start-server"]
セットアップのスクリプト
#!/bin/bash
source /etc/profile.d/rbenv.sh
mkdir -p /var/www
git clone git@github.com:k-shogo/rails4-ajax-sample.git /var/www/rails
cd /var/www/rails
bundle install --without test development --path vendor/bundle
bundle exec rake db:create RAILS_ENV=production
bundle exec rake db:migrate RAILS_ENV=production
bundle exec rake master:import RAILS_ENV=production
bundle exec rake assets:precompile RAILS_ENV=production
source /etc/profile.d/rbenv.sh
しておけばいいじゃんってことです。
この構成でも不満
たしかに毎回rubyビルドする部分は解消されたのですが、気になるポイントも出てきました。
- 毎回
git clone
する、つまりrailsリポジトリ変更したらpushしてからコンテナビルドし直さなきゃいけない - ブランチ切り替えて試したいときにわざわざDockerfile編集?
- セットアップのロジックをrailsリポジトリじゃなくて動作させる側のコンテナに持たせるのが不自然
現在の構成
そもそもDockerでrbenvいらないじゃん + railsリポジトリの中にDockerfileがあった方がいいねってことで、ruby, node等を用意したコンテナ + railsリポジトリ(Dockerfile同梱) の2リポジトリ構成にしてみました。
ベースとなるコンテナのDockerfile
FROM dockerfile/ubuntu
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y && \
DEBIAN_FRONTEND=noninteractive apt-get -y install \
build-essential \
curl \
git-core \
libcurl4-openssl-dev \
libreadline-dev \
libssl-dev \
libxml2-dev \
libxslt1-dev \
libyaml-dev \
zlib1g-dev && \
curl -O http://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz && \
tar -zxvf ruby-2.1.2.tar.gz && \
cd ruby-2.1.2 && \
./configure --disable-install-doc && \
make && \
make install && \
cd .. && \
rm -r ruby-2.1.2 ruby-2.1.2.tar.gz && \
echo 'gem: --no-document' > /usr/local/etc/gemrc
# Install Bundler for each version of ruby
RUN \
echo 'gem: --no-rdoc --no-ri' >> /.gemrc && \
gem install bundler
# install sqlite3
RUN apt-get install -y sqlite3 libsqlite3-dev
# Install Node.js and npm
RUN \
apt-get install -y software-properties-common && \
apt-get update && \
add-apt-repository -y ppa:chris-lea/node.js && \
echo "deb http://archive.ubuntu.com/ubuntu precise universe" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y nodejs
もうsource /etc/profile.d/rbenv.sh
やらbash -l -c
とかいりません
railsリポジトリに同梱するDockerfile
FROM kshogo/docker-rails-base
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle config without test development doc
RUN bundle install
ADD . /myapp
ENV RAILS_ENV production
RUN bundle exec rake db:create
RUN bundle exec rake db:migrate
RUN bundle exec rake assets:precompile
# Add configuration files in repository to filesystem
ADD dotenv /myapp/.env
ADD start-server.sh /usr/bin/start-server
RUN chmod +x /usr/bin/start-server
EXPOSE 3000
ENTRYPOINT ["/usr/bin/start-server"]
ポイントはrailsアプリ全体をADDする前に、Gemfile
とGemcile.lock
だけをADDしてbundle install
している箇所です。
これによって、Gemfileに更新があった場合のみbundle install
が実行されるようになります。
.dockerignore
実はこの構成にした直後、「ローカルでdocker build
したときにvendor/bundle
以下にインストールされたgemのファイル群までADDされて無駄」、「Dockerfileの中で.bundle/config
を消さないと設定を引き継いでしまう」などの不満点もあり、gitignoreファイルみたいに除外指定したいなーと思っていたら、docker 1.1.0でdockerignoreファイルに対応されたのでその不満も解消しました。
### Rails ###
public/assets/*
## Environment normalisation:
.bundle
vendor/bundle
ポイント
- ADDがキャッシュされるのでGemfileが変更されてなければ
bundle install
がスキップされる git checkout
すればブランチ違いでのbuildが簡単
まとめ
railsリポジトリにDockerfileを同梱した方が簡単だったというのが感想です。
dockerの開発も活発で、ほしいなーと思った機能が早速docker 1.1.0に含まれていたのには感心しました。
今回はお試しということでrailsコンテナの中にsqliteでDBも内蔵してしまっていますが、本来はDBコンテナは分離した方がいいですね。