Rails の Dockerイメージを軽量化してみる

鉄板すぎる話ですが、自分の手元で確かめたかったのでメモに残します。

1. Alpine Linux を利用する
1. RUN 命令などを削減する
1. マルチステージビルド
1. 全部盛り

## 何もしない状態

[Docker ドキュメント](https://docs.docker.jp/compose/rails.html#id2) を参考に作成。

```Dockerfile
FROM ruby:2.6.8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN gem install bundler:2.2.16 
RUN bundle install
COPY . /app

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

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

```

サイズは1GB超え。

```terminal
$ docker image ls
REPOSITORY         TAG               IMAGE ID       CREATED          SIZE
vocabook_api_app   latest            005fb8bfbdae   4 minutes ago    1.11GB
```

## Alpine Linux を利用する

Alpine Linux は、コンテナでよく登場する軽量な Linux ディストリビューション。
apk というパッケージマネージャを利用するため、サブコマンドやパッケージ名を修正する。

注意：
すでに Debian ベースで動かしているサービスをコンテナ化する場合などは、Debian ベースの軽量なディストリビューション（stretch や buster）の利用が推奨されます。

```Dockerfile
FROM ruby:2.6.8-alpine3.14
RUN apk update -qq && apk add --no-cache build-base postgresql-dev nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN gem install bundler:2.2.16 
RUN bundle install
COPY . /app

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

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

```

何もしない状態から 300MB 削減。

```terminal
$ docker image ls
REPOSITORY         TAG               IMAGE ID       CREATED          SIZE
vocabook_api_app   latest            24268673b3c8   43 seconds ago   799MB
```

## RUN 命令などを削減する

RUN や COPY などの命令によってレイヤと呼ばれる中間イメージが作成される。
命令数を減らすことで生成されるレイヤを削減し、イメージの軽量化を図る。

```Dockerfile
FROM ruby:2.6.8
RUN apt-get update -qq && \
    apt-get install -y build-essential libpq-dev nodejs && \
    mkdir /app
WORKDIR /app
COPY Gemfile Gemfile.lock /app/
RUN gem install bundler:2.2.16 && bundle install
COPY . /app

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

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

```

この程度の命令数ではサイズは変わらず。

```terminal
$ docker image ls
REPOSITORY         TAG               IMAGE ID       CREATED              SIZE
vocabook_api_app   latest            3b3100aef7b1   About a minute ago   1.11GB
```

## マルチステージビルド

`bundle install`で分けている[記事](https://zenn.dev/tatsurom/articles/multi-stage-build)があったので参考に試してみる。
パッケージをインストールするステージ、そのパッケージをコピーして持ってくるステージに分ける。

```Dockerfile
FROM ruby:2.6.8 AS builder
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN gem install bundler:2.2.16 
RUN bundle install

FROM ruby:2.6.8 AS runner
RUN apt-get update -qq && apt-get install -y libpq-dev nodejs
RUN mkdir /app
WORKDIR /app
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY . /app

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

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

```

何もしていない状態から 20MB 程度減らすことができた。

```terminal
$ docker image ls
REPOSITORY         TAG               IMAGE ID       CREATED          SIZE
vocabook_api_app   latest            376df67aec60   35 minutes ago   1.09GB
```

history コマンドでレイヤのサイズを見てみると、確かに 20MB くらい削減されている。

上:何もしないとき ｜ 下:マルチステージビルド

```terminal
<missing>      4 hours ago          RUN /bin/sh -c bundle install # buildkit        103MB     buildkit.dockerfile.v0
```


```
<missing>      11 minutes ago   COPY /usr/local/bundle /usr/local/bundle # b…   86.4MB    buildkit.dockerfile.v0
```


マルチステージビルドは、コンパイルしてバイナリを生成するようなケースだと分かりやすく効果が出そう。

## 全部盛り

```Dockerfile
FROM ruby:2.6.8-alpine3.14 AS builder
RUN apk update -qq && \
    apk add --no-cache build-base postgresql-dev nodejs && \
    mkdir /app
WORKDIR /app
COPY Gemfile Gemfile.lock /app/
RUN gem install bundler:2.2.16 && bundle install

FROM ruby:2.6.8-alpine3.14 AS runner
RUN apk update -qq && \
    apk add --no-cache postgresql-dev nodejs && \
    mkdir /app
WORKDIR /app
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY . /app

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

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

```

めっちゃ小さくなった。何もしない状態の半分近くまで小さくなっている。

```terminal
$ docker images
REPOSITORY         TAG               IMAGE ID       CREATED             SIZE
vocabook_api_app   latest            4aebe416674b   2 minutes ago       588MB
```

history を見てみると、`apk add`のところで差分が出ていそうなので、`build-base` を最終イメージに持ってこなかったのが良かったみたい。

```terminal
<missing>      6 minutes ago   RUN /bin/sh -c apk update -qq &&     apk add…   354MB     buildkit.dockerfile.v0
```

```terminal
<missing>      6 hours ago     RUN /bin/sh -c apk update -qq && apk add --n…   547MB     buildkit.dockerfile.v0
```

## 参考

https://qiita.com/gorohash/items/2358b4b9e0c708e510d0

https://tech.drecom.co.jp/migrate-rails-app-to-container/