こたつとみかんとプログラミング

33才実務未経験ですがウェブエンジニアにジョブチェンジするために勉強したことをアップするためのブログです。

Docker の image を軽くする

Docker に無事既存の web アプリを載せることはできたものの、めちゃ重い。web アプリのイメージだけで 5GB もあった。
イメージを軽くするには、ベースのイメージを alpine Linux なるものに変えると軽くなるということだったので、変えてみた。

# Node.js & Yarn
FROM node:10.15.1-alpine as node

RUN apk add --no-cache --virtual .ruby-builddeps bash curl \
  && curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.4

# Ruby & Bundler & postgresql-client
FROM ruby:2.6.5-alpine

COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn

ENV RUN_PACKAGES="nodejs postgresql postgresql-dev tzdata" \
    DEV_PACKAGES="build-base curl-dev gcc libc-dev libxml2-dev linux-headers make" \
    CHROME_PACKAGES="chromium chromium-chromedriver dbus mesa-dri-swrast ttf-freefont udev wait4ports xorg-server xvfb zlib-dev"

RUN apk --update --no-cache add ${RUN_PACKAGES} \
  && apk --update --no-cache add ${DEV_PACKAGES} \
  && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && apk del --purge tzdata

ENV ENTRYKIT_VERSION 0.4.0

# Entrykit & ChromeDriver
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 \
  && apk --update --no-cache add ${CHROME_PACKAGES}

# 作業ディレクトリの作成、設定
RUN mkdir /ketsuatsu_app
WORKDIR /ketsuatsu_app

# bundle install & Delete Unnecessary Packages_and_Cache & Copy File
COPY Gemfile /ketsuatsu_app/Gemfile
COPY Gemfile.lock /ketsuatsu_app/Gemfile.lock
RUN gem install bundler --version 2.1.2 \
  && bundle install --path vendor/bundle \
  && find vendor/bundle/ruby -path '*/gems/*/ext/*/Makefile' -exec dirname {} \; | xargs -n1 -P$(nproc) -I{} make -C {} clean \
  && apk del ${DEV_PACKAGES} \
  && apk del .ruby-builddeps
COPY . /ketsuatsu_app

ENTRYPOINT [ \
  "prehook", "bundle install -j3 --path vendor/bundle", "--", \
  "prehook", "ruby -v", "--", \
  "prehook", "node -v", "--" \
]
# Node.js & Yarn
FROM node:10.15.1-alpine as node

RUN apk add --no-cache --virtual .ruby-builddeps bash curl \
  && curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.4

最初に Node.js 10.15.1 のベースイメージを読み込み、nodeという名前をイメージにつけている。
次に alpine では使えない bashcurl コマンドをイメージに加えてコマンドを使えるようにして、
Yarn 1.22.4 をインストールしている。

このイメージには、下記のフォルダに Node.js と Yarn がインストールされている。

/usr/local/bin/node
/opt/yarn-v1.21.1
# Ruby & Bundler & postgresql-client
FROM ruby:2.6.5-alpine

COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn

次に、ruby 2.6.5 のベースイメージを読み込み、先ほど作ったnodeイメージの Node.js と Yarn を ruby のイメージにコピーしている。
そして、ln -s コマンドでシンボリックリンクを作成している。
Node.js は /usr/local/bin/node に、 Yarn は /usr/local/bin/yarn にコピーされたことになる。

ENV RUN_PACKAGES="nodejs postgresql postgresql-dev tzdata" \
    DEV_PACKAGES="build-base curl-dev gcc libc-dev libxml2-dev linux-headers make" \
    CHROME_PACKAGES="chromium chromium-chromedriver dbus mesa-dri-swrast ttf-freefont udev wait4ports xorg-server xvfb zlib-dev"

複数のパッケージを変数に格納してあとで使いやすくしている。

RUN apk --update --no-cache add ${RUN_PACKAGES} \
  && apk --update --no-cache add ${DEV_PACKAGES} \
  && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && apk del --purge tzdata

先ほど変数に格納した必要なパッケージをインストール。
RUN と DEV に分けているのは、 RUN の方はそのまま Docker 上で使用するが、 DEV の方は環境構築が終わったら必要なくなるパッケージなので、あとで削除するためにこの分け方をしている。
apk del --purge パッケージ名 でそのパッケージに関連するファイルもまとめて削除できる。

# Entrykit & ChromeDriver
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 \
  && apk --update --no-cache add ${CHROME_PACKAGES}

Entrykit と ChromeDriver のインストール。
前回は ChromeDriver のインストールのために直接 URL を叩いていたが、 apt-get から apk に変わったとき、ChromeDriver インストール時に必要になる apt-key というコマンドが使用できないことが判明。これを何に置き換えれば良いかわからず苦戦したが、別の記事で必要なパッケージを読み込む方法があったので、そちらを採用した。

# bundle install & Delete Unnecessary Packages_and_Cache & Copy File
COPY Gemfile /ketsuatsu_app/Gemfile
COPY Gemfile.lock /ketsuatsu_app/Gemfile.lock
RUN gem install bundler --version 2.1.2 \
  && bundle install --path vendor/bundle \
  && find vendor/bundle/ruby -path '*/gems/*/ext/*/Makefile' -exec dirname {} \; | xargs -n1 -P$(nproc) -I{} make -C {} clean \
  && apk del ${DEV_PACKAGES} \
COPY . /ketsuatsu_app

この箇所はあまり変わりないが、最後にファイルコピーの前に必要ないパッケージとキャッシュを削除している。

また、ここでは割愛するが、docker-compose.yml の方でもdbのイメージに alpine を採用している。
以上でビルドした結果、5GBあったのが2.5GBくらいまで削減できた。

しかし、エラーが多数発生。今回はローカル環境で作った既存アプリを dockerに乗せて軽量化しようとしたが、Docker のイメージのほとんどは Ubuntu 環境であり、ローカル(Mac OS / つまり unix)環境のものを乗せようとするとうまく表示されないとのこと。
やるなら、最初から Docker 上で作るときのみ alpine は使った方が良さそう。

参考記事
Dockerのマルチステージビルドを使う - Qiita
Docker イメージサイズを抑えながら Ruby on Rails + PostgreSQL の開発環境を作成する - bitA Tech Blog
Rails 6.0 × MySQL8でDocker環境構築(Alpineベース) - Qiita
RailsのDockerイメージを一番小さくする方法 - Qiita