BETA

Rails用のDockerイメージのサイズを小さくする

投稿日:2020-06-21
最終更新:2020-07-13

修正前

1.34GB

FROM ruby:2.7.1-buster  

ENV PROJECT_ROOT /myapp  

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -  
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list  
RUN apt-get update && apt-get install -y yarn  

RUN mkdir ${PROJECT_ROOT}  
WORKDIR ${PROJECT_ROOT}  
COPY Gemfile Gemfile.lock package.json yarn.lock ${PROJECT_ROOT}/  
RUN bundle install  
RUN yarn install  
COPY . ${PROJECT_ROOT}/  

COPY docker/web/entrypoint.sh /usr/bin/  
RUN chmod +x /usr/bin/entrypoint.sh  
ENTRYPOINT ["entrypoint.sh"]  
EXPOSE 3000  

CMD ["rails", "server", "-b", "0.0.0.0"]  

修正方針

  1. 親イメージをAlpine Linuxに変更
  2. マルチステージビルドの導入
  3. その他の調整

親イメージをAlpine Linuxに変更

@@ -1,4 +1,4 @@  
-FROM ruby:2.7.1-buster  
+FROM ruby:2.7.1-alpine3.12  

 ENV PROJECT_ROOT /myapp  

親イメージの変更に伴う修正

yarnのインストール方法の修正

参考:https://classic.yarnpkg.com/en/docs/install#alpine-stable

@@ -2,9 +2,7 @@ FROM ruby:2.7.1-alpine3.12  

 ENV PROJECT_ROOT /myapp  

-RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -  
-RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list  
-RUN apt-get update && apt-get install -y yarn  
+RUN apk add --no-cache yarn  

 RUN mkdir ${PROJECT_ROOT}  
 WORKDIR ${PROJECT_ROOT}  

nokogiri(gem)のインストールに必要なパッケージの追加

参考:https://nokogiri.org/tutorials/installing_nokogiri.html#ruby-on-alpine-linux-docker

@@ -2,7 +2,9 @@ FROM ruby:2.7.1-alpine3.12  

 ENV PROJECT_ROOT /myapp  

-RUN apk add --no-cache yarn  
+RUN apk add --no-cache \  
+    build-base \  
+    yarn  

 RUN mkdir ${PROJECT_ROOT}  
 WORKDIR ${PROJECT_ROOT}  

mysql2(gem)のインストールに必要なパッケージの追加

参考:https://github.com/brianmario/mysql2#linux-and-other-unixes

You may need to install a package such as libmariadb-dev, libmysqlclient-dev, mysql-devel, or default-libmysqlclient-dev; refer to your distribution's package guide to find the particular package.

とのこと。

$ docker run -it ruby:2.7.1-alpine3.12 sh  

# apk update  

# apk search mysql | grep dev  
mariadb-connector-c-dev-3.1.8-r1  
mariadb-dev-10.4.13-r0  

# apk info -a mariadb-dev  
mariadb-dev-10.4.13-r0 description:  
A fast SQL database server (development files)  

mariadb-dev-10.4.13-r0 webpage:  
https://www.mariadb.org/  

mariadb-dev-10.4.13-r0 installed size:  
6238208  

mariadb-dev-10.4.13-r0 depends on:  
openssl-dev  
zlib-dev  
mariadb-connector-c-dev  
mariadb-embedded=10.4.13-r0  
pkgconfig  

mariadb-dev-10.4.13-r0 provides:  
mysql-dev=10.4.13-r0  
pc:mariadb=10.4.13  

mariadb-dev-10.4.13-r0 has auto-install rule:  

mariadb-dev-10.4.13-r0 license:  
GPL-2.0-or-later  

上記の結果を見ると、mariadb-devを追加すれば良いと思われる。

@@ -4,6 +4,7 @@ ENV PROJECT_ROOT /myapp  

 RUN apk add --no-cache \  
     build-base \  
+    mariadb-dev \  
     yarn  

 RUN mkdir ${PROJECT_ROOT}  

シェルスクリプトの修正

参考:https://qiita.com/junun/items/aba81e98438296bc78b0

コンテナの起動時に以下のようなエラーが出た。

standard_init_linux.go:211: exec user process caused "no such file or directory"  

エントリーポイントのシェルスクリプトのshebangで、bashを指定していたのが原因だった。
Alpine Linuxにbashはないようなので、shに修正した。

@@ -1,4 +1,4 @@  
-#!/bin/bash  
+#!/bin/sh  
 set -e  

 # Remove a potentially pre-existing server.pid for Rails.  

アプリケーションの起動に必要なパッケージの追加

アプリケーションの起動時に以下のようなエラーが出た。

/usr/local/bundle/gems/tzinfo-1.2.7/lib/tzinfo/data_source.rb:182:in `rescue in create_default_data_source': tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install (TZInfo::DataSourceNotFound)  

tzinfo(gem)のwikiを見ると、

On most Unix-based systems (e.g. Linux), TZInfo is able to use the system zoneinfo directory as a source of data.

とのことなので、tzdataパッケージを追加した。

@@ -5,6 +5,7 @@ ENV PROJECT_ROOT /myapp  
 RUN apk add --no-cache \  
     build-base \  
     mariadb-dev \  
+    tzdata \  
     yarn  

 RUN mkdir ${PROJECT_ROOT}  

親イメージの変更の結果

1.34GB -> 634MB

マルチステージビルドの導入

@@ -1,19 +1,29 @@  
-FROM ruby:2.7.1-alpine3.12  
+FROM ruby:2.7.1-alpine3.12 AS base  

 ENV PROJECT_ROOT /myapp  

 RUN apk add --no-cache \  
-    build-base \  
-    mariadb-dev \  
     tzdata \  
     yarn  

 RUN mkdir ${PROJECT_ROOT}  
 WORKDIR ${PROJECT_ROOT}  
+  
+FROM base AS builder  
+  
+RUN apk add --no-cache \  
+    build-base \  
+    mariadb-dev  
+  
 COPY Gemfile Gemfile.lock package.json yarn.lock ${PROJECT_ROOT}/  
 RUN bundle install  
 RUN yarn install  
+  
+FROM base  
+  
 COPY . ${PROJECT_ROOT}/  
+COPY --from=builder ${GEM_HOME}/ ${GEM_HOME}/  
+COPY --from=builder ${PROJECT_ROOT}/node_modules/ ${PROJECT_ROOT}/node_modules/  

 COPY docker/web/entrypoint.sh /usr/bin/  
 RUN chmod +x /usr/bin/entrypoint.sh  

マルチステージビルドの導入に伴う修正

mysql2が依存しているランタイムライブラリの追加

アプリケーションの起動時に以下のようなエラーが出た。

/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require': Error loading shared library libmariadb.so.3: No such file or directory (needed by /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so) - /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so (LoadError)  

libmariadb.so.3が必要とのこと。

$ docker run -it ruby:2.7.1-alpine3.12 sh  

# apk update  

# apk search libmariadb  
mariadb-connector-c-dev-3.1.8-r1  
mariadb-embedded-10.4.13-r0  
mariadb-connector-c-3.1.8-r1  

# apk info -a mariadb-connector-c  
mariadb-connector-c-3.1.8-r1 description:  
The MariaDB Native Client library (C driver)  

mariadb-connector-c-3.1.8-r1 webpage:  
https://mariadb.org/  

mariadb-connector-c-3.1.8-r1 installed size:  
479232  

mariadb-connector-c-3.1.8-r1 depends on:  
so:libc.musl-x86_64.so.1  
so:libcrypto.so.1.1  
so:libssl.so.1.1  
so:libz.so.1  

mariadb-connector-c-3.1.8-r1 provides:  
so:libmariadb.so.3=3  

mariadb-connector-c-3.1.8-r1 has auto-install rule:  

mariadb-connector-c-3.1.8-r1 license:  
LGPL-2.1-or-later  

上記の結果を見ると、mariadb-connector-cを追加すれば良いと思われる。

@@ -3,6 +3,7 @@ FROM ruby:2.7.1-alpine3.12 AS base  
 ENV PROJECT_ROOT /myapp  

 RUN apk add --no-cache \  
+    mariadb-connector-c \  
     tzdata \  
     yarn  

追加前

# ldd /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so 2>&1 | head  
    /lib/ld-musl-x86_64.so.1 (0x7f64664a4000)  
    libruby.so.2.7 => /usr/local/lib/libruby.so.2.7 (0x7f64660c4000)  
Error loading shared library libmariadb.so.3: No such file or directory (needed by /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so)  
    libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f64664a4000)  
    libz.so.1 => /lib/libz.so.1 (0x7f64660aa000)  
    libgmp.so.10 => /usr/lib//libgmp.so.10 (0x7f6466043000)  
Error relocating /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so: mysql_stmt_free_result: symbol not found  
Error relocating /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so: mysql_fetch_row: symbol not found  
Error relocating /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so: mysql_set_local_infile_handler: symbol not found  
Error relocating /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so: mysql_num_fields: symbol not found  

追加後

# ldd /usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/mysql2.so  
    /lib/ld-musl-x86_64.so.1 (0x7f9059954000)  
    libruby.so.2.7 => /usr/local/lib/libruby.so.2.7 (0x7f9059574000)  
    libmariadb.so.3 => /usr/lib//libmariadb.so.3 (0x7f9059525000)  
    libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f9059954000)  
    libz.so.1 => /lib/libz.so.1 (0x7f905950b000)  
    libgmp.so.10 => /usr/lib//libgmp.so.10 (0x7f90594a4000)  
    libssl.so.1.1 => /usr/lib//libssl.so.1.1 (0x7f9059423000)  
    libcrypto.so.1.1 => /usr/lib//libcrypto.so.1.1 (0x7f90591a4000)  

備考

今回は該当しなかったが、nokogiri(gem)のインストール時、システムのlibxml2, libxsltを使う場合は、ランタイムライブラリとしてそれらの追加も必要だと思われる。

マルチステージビルドの導入の結果

634MB -> 300MB

その他の調整

bundlerのキャッシュの削除

参考:https://nokogiri.org/tutorials/installing_nokogiri.html#ruby-on-alpine-linux-docker_1

@@ -17,7 +17,8 @@ RUN apk add --no-cache \  
     mariadb-dev  

 COPY Gemfile Gemfile.lock package.json yarn.lock ${PROJECT_ROOT}/  
-RUN bundle install  
+RUN bundle install && \  
+    rm -rf ${GEM_HOME}/cache  
 RUN yarn install  

 FROM base  

300MB -> 281MB

.dockerignoreファイルの追加

@@ -0,0 +1,3 @@  
+.git  
+.gitignore  
+node_modules  

281MB -> 280MB

イメージのサイズを小さくする効果はほとんどなかったが、node_modulesを除外したことで、DockerfileのCOPY命令の順番を入れ替えることができて、Dockerのキャッシュが効きやすくなったと思われる。

@@ -23,9 +23,9 @@ RUN yarn install  

 FROM base  

-COPY . ${PROJECT_ROOT}/  
 COPY --from=builder ${GEM_HOME}/ ${GEM_HOME}/  
 COPY --from=builder ${PROJECT_ROOT}/node_modules/ ${PROJECT_ROOT}/node_modules/  
+COPY . ${PROJECT_ROOT}/  

 COPY docker/web/entrypoint.sh /usr/bin/  
 RUN chmod +x /usr/bin/entrypoint.sh  

不要なgemの除外

参考:https://bundler.io/v2.0/man/bundle-config.1.html#LIST-OF-AVAILABLE-KEYS

@@ -12,6 +12,8 @@ WORKDIR ${PROJECT_ROOT}  

 FROM base AS builder  

+ARG BUNDLE_WITHOUT  
+  
 RUN apk add --no-cache \  
     build-base \  
     mariadb-dev  
  • テスト環境( --build-arg BUNDLE_WITHOUT=development
    280MB -> 270MB
  • 本番環境( --build-arg BUNDLE_WITHOUT=development:test
    280MB -> 262MB

最終的な修正結果

  • 開発環境
    1.34GB -> 280MB
  • テスト環境
    1.34GB -> 270MB
  • 本番環境
    1.34GB -> 262MB
FROM ruby:2.7.1-alpine3.12 AS base  

ENV PROJECT_ROOT /myapp  

RUN apk add --no-cache \  
    mariadb-connector-c \  
    tzdata \  
    yarn  

RUN mkdir ${PROJECT_ROOT}  
WORKDIR ${PROJECT_ROOT}  

FROM base AS builder  

ARG BUNDLE_WITHOUT  

RUN apk add --no-cache \  
    build-base \  
    mariadb-dev  

COPY Gemfile Gemfile.lock package.json yarn.lock ${PROJECT_ROOT}/  
RUN bundle install && \  
    rm -rf ${GEM_HOME}/cache  
RUN yarn install  

FROM base  

COPY --from=builder ${GEM_HOME}/ ${GEM_HOME}/  
COPY --from=builder ${PROJECT_ROOT}/node_modules/ ${PROJECT_ROOT}/node_modules/  
COPY . ${PROJECT_ROOT}/  

COPY docker/web/entrypoint.sh /usr/bin/  
RUN chmod +x /usr/bin/entrypoint.sh  
ENTRYPOINT ["entrypoint.sh"]  
EXPOSE 3000  

CMD ["rails", "server", "-b", "0.0.0.0"]  
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@mas61018の技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう