はじめに
どうも!生産技術部で製品の検査工程を担当しているエンジニアです。今流行の煽り商法ですが、中身は有益な情報を詰め込んでいますので最後までお付き合いください。
Docker ComposeでRuby on Railsの開発環境を整えていく中で、CIの実行時間がネックになる課題に取組み改善を行いました。改善によって、8分の実行時間を4分まで削減する事ができました。
本記事では、CIを倍早くするために必要な5つの方法を紹介します。紹介の中には、各ステップの参考実装と、スムーズに改善できるようなエッセンスを散りばめてあります。
改善結果は以下の通りです。上から進めていき5番目まで実践する事で、倍早くなり、幅広いシチュエーションで柔軟に対応が可能な仕組みを実現することができます。
- CIの改善
index | 改善内容 | CI実行時間 |
---|---|---|
0 | CIの改善なし | 00:08:01 |
1 | キャッシュの利用 | 00:03:21 |
2.1 | CIパイプラインで並列化(Artifactsの利用) | 00:06:50 |
2.2 | CIパイプラインで並列化(registoryの利用) | 00:04:42 |
3 | BuildKitの利用 | 00:04:51 |
- Dockerfileの改善
index | 改善内容 | イメージサイズ | CI実行時間 |
---|---|---|---|
- | Dockerfileの改善なし | 1.61 GB | 00:04:51 |
4 | Alpineベースに変更 | 857.22 MB | 00:04:04 |
5 | マルチステージビルドに変更 | 568.92 MB | 00:03:54 |
環境
CIの実行には普段から利用させていただいており愛着のあるGitLabを利用しています。具体的に作成しているアプリの中身については触れませんが、アプリケーション、データベース、ウェブで構成され、appは、ruby on rails、vue.js、pumaとbootstrapやfontawesomeを含み、dbはpostgreSQL、webはnginxとなっております。これらはdocker-composeを利用して構成管理します。
Docker Composeは、app、db、webディレクトリ直下のDockerfileを使ってビルドします。
version: "3.9"
services:
app:
build:
context: ./app
container_name: app01
volumes:
- ./app:/make-it-quick
- web-socket:/make-it-quick/tmp/sockets
depends_on:
- db
db:
container_name: db01
build: ./db
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
web:
container_name: web01
build: ./web
volumes:
- ./web/config/nginx.conf:/etc/nginx/conf.d/make-it-quick.conf
- web-socket:/tmp/sockets
ports:
- 80:80
depends_on:
- app
volumes:
web-socket:
db-data:
読み進め方
章ごとに以下のような構成で記載しています。参考実装には、改善を実施した結果のCIスクリプトもしくはDockerfileへのリンクが貼ってあります。ポイントは、主に自分が実装していく中で詰まった箇所が記載されています。ポイントで説明した箇所に関するプログラムを抜粋してありますので、説明と合わせてみてください。
[参考実装] CI実行時間(00:08:01)
簡単な技術説明と技術的アプローチの説明
参考サイト様へのURLリンク
ポイント:
- ~
- ~
プログラム抜粋
CIの改善
まずは、工夫せずにCIを実装しますが、下記ドキュメントに記載されている通り、素のShellを利用してDockerのCIを実行するアプローチと、Docker in Docker用のdindというDockerイメージ上でDockerのCIを実行するアプローチがあり、どちらかを選択する必要があります。今回は、推奨であるdindを利用します。dindは処理速度が課題として挙げられていますので、なんとかしていきたいと思います。
ポイント:
- dindにはDockerのツールがあらかじめ準備された状態で使用することが出来ます。しかし、docker-composeやdockerizeは含まれていませんので、準備する必要があります。
-
docker-compose up -d
でDockerを立ち上げた直後にテストを実行すると、テストが実行できたり出来なかったりと不安定になります。これはDockerが立ち上がり切っていないことが原因です。dockerizeを利用して、立ち上がったことを確認してからテストを実行しましょう。 - dindで、dockerizeする時のurlがlocalhostではなくdockerになっていることにも注意しましょう。
image: docker:latest
services:
- docker:dind
variables:
DOCKERIZE_VERSION: v0.6.1 # dockerizeのバージョンを指定する
before_script:
- (略)
- apk add docker-compose # docker-composeを入れる
- apk add --no-cache openssl
&& wget https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz
&& tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz
&& rm dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz # dockerizeを入れる
test:
stage: test
script:
- docker-compose up -d
- dockerize -wait tcp://docker:80 -timeout 1m # dockerizeで待つ
- docker-compose run --rm app yarn test
1.キャッシュの利用 Docker Layer Cache(DLC)
Dockerでは、各コマンドはレイヤーとなり、レイヤーはキャッシュとして保存されます。そして、ビルド時にレイヤーの変更がなければ再利用できることは、様々なDocker関連記事からご存知かと思います。この仕組みにより、Dockerでは既存のイメージをキャッシュとして利用できるため、大幅な高速化が可能になります。GitLabには、Dockerのイメージを保存するためのDocker registoryがサービスとして用意されています。キャッシュ用のイメージの保存先は、DockerHubや他のレジストリでも構いません。
DockerレイヤーキャッシングでDocker-in-Dockerビルドを高速化する
ポイント:
-
docker build
では--cache-from
でイメージを指定することで、指定したイメージをキャッシュとして利用できます。しかし、docker-compose build
には、--cache-from
が無いため、指定することが出来ません。そこで、Docker Composeで利用するイメージをdocker build
を使って指定したイメージからビルドしておき、docker-composeで立ち上げる事により、指定したイメージのキャッシュが利用できるのと同じ状態を作る事ができます。 - Docker Composeでは、Dockerとビルド方法が異なるため、Dockerとキャッシュが異なります。そのためDockerのビルド結果をDocker Composeで使用するといった事ができません。しかし、
COMPOSE_DOCKER_CLI_BUILD
を有効にする事で、DockerのCLIが使用されるため、Dockerのビルド方法を利用できる様になります。これにより、DockerとDocker Composeでキャッシュを共用できます。 - よし!DLC使って4分切ったから問題なし!と言う訳には行きません。テスト以外のステージにもイメージを持って回る必要があります。docker-compose内に複数のイメージがある場合、ビルド時間が多くかかってしまいます。並列化できる仕組みにしていきましょう。
before_script:
- (略)
# Use gitlab docker registry
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY # レジストリにログイン
test:
stage: test
script:
# Caching docker layer
- docker pull $CI_REGISTRY_IMAGE/app:latest || true # レジストリからイメージを取ってくる
- docker build
--cache-from $CI_REGISTRY_IMAGE/app:latest
--tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE/app:latest
--tag make-it-quick_app
./app # 取ってきたイメージをキャッシュに追加(--cache-from)してビルドする
# 最後のタグ(make-it-quick_app)は、docker-compose up -dで利用するためのタグ
- docker push $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE/app:latest # 次のビルド時キャッシュとして利用するため、レジストリに保存
- COMPOSE_DOCKER_CLI_BUILD=1 docker-compose up -d # Docker cliを利用
2.1.CIパイプラインで処理の並列化(Artifactsの利用)
3つのイメージをビルドし、テストに使いたい場合を考えます。ビルドとテストのステージを分割し、CIの前段のステージで3つのイメージを並列にビルドする。テストのステージでビルド結果を利用することでビルド時間の短縮が狙えます。ビルド結果を次のステージに渡す方法は、ビルド結果をtarに圧縮してArtifacts(成果物)として設定し、次のステージで利用します。感のいい人なら既に気づいていると思いますが、今から実施する方法は失敗です。
Passing Docker Image between Build and Test stage in gitlab-runner
この方法は、パイプラインを使った並列化には成功しているものの、tarに圧縮する工程、圧縮したものを展開してDockerで利用できる形にする工程で時間がかかり、実行時間は7分程度まで増加してしまいます。
build-app:
stage: build
script:
- (略)
- docker build --cache-from $CI_REGISTRY_IMAGE/app:latest --tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE/app:latest --tag make-it-quick_app ./app
- (略)
- mkdir -p docker_images
- docker save make-it-quick_app > docker_images/make-it-quick_app.tar # tarに圧縮
artifacts:
paths:
- docker_images
build-db:
(略)
build-web:
(略)
test:
stage: test
script:
- docker load < docker_images/make-it-quick_app.tar # tarを展開しロード
- docker load < docker_images/make-it-quick_db.tar
- docker load < docker_images/make-it-quick_web.tar
2.2.CIパイプラインで処理の並列化(GitLab registoryの利用)
キャッシュの利用を思い出すと簡単なことですが、次のステージでもレジストリに保存したイメージを利用すれば、テストステージで再度ビルドし直しても、レイヤーに変更が無いため、時間を消費することなく利用可能と言うことになります。このことに気がつくまでに大分時間がかかってしまいました。理解力のある人なら、すぐにこの解法に気が付いたでしょう。
ポイント:
- テストステージでは、コミットハッシュのタグがついたイメージを利用するところになります。latestは、後から実行したCIによって書き換えられてしまう可能性があるからです。
- 「キャッシュの利用」よりも1分程度の時間が伸びてしまっている理由は、マルチステージ化によって
docker pull/push
の回数が増えたことが原因となります。イメージサイズが大きくなるほど、docker pull/push
の時間は無視できないものとなります。この問題については、Dockerイメージの改善で取り上げています。
build-app:
stage: build
script:
# Using DLC
- docker pull $CI_REGISTRY_IMAGE/app:latest || true
- docker build --cache-from $CI_REGISTRY_IMAGE/app:latest --tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE/app:latest ./app
- docker push $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA # コミットハッシュのついたイメージをレジストリに保存
- docker push $CI_REGISTRY_IMAGE/app:latest
build-db:
(略)
build-web:
(略)
test:
stage: test
script:
# コミットハッシュのついたイメージを取得
- docker pull $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA || true
- docker pull $CI_REGISTRY_IMAGE/db:$CI_COMMIT_SHA || true
- docker pull $CI_REGISTRY_IMAGE/web:$CI_COMMIT_SHA || true
# 再度ビルドし直すが、変更なしのためロスタイムなし
- docker build --cache-from $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA --tag make-it-quick_app ./app
- docker build --cache-from $CI_REGISTRY_IMAGE/db:$CI_COMMIT_SHA --tag make-it-quick_db ./db
- docker build --cache-from $CI_REGISTRY_IMAGE/web:$CI_COMMIT_SHA --tag make-it-quick_web ./web
3.BuildKitの利用
BuildKitは、18.09以降のDockerから導入されたビルドツールであり、docker build
に対して、性能、ストレージ管理、特徴的な機能性、セキュリティに関する改善に繋がります。BuildKitは、Dockerとは別のプロジェクトであるMody BuildKitで開発されたものになります。下記に記載されている通り、DockerではDOCKER_BUILDKIT=1 docker build .
と環境変数を設定することでBuildKitが有効になりますが、全てのBuildKitの機能をサポートしたものではありません。さらにLinuxコンテナのみのサポートとなります。Docker 19.03から導入されたBuildxというビルドツールは、Mody BuildKitに完全対応したものになります。そして、ユーザがビルドに利用した環境(アーキテクチャやプラットフォーム)に限らず、様々なプラットフォームに対するイメージをビルドする事が可能になります。ただし、現状のBuildxは試験的な機能となっています。
ポイント:
- Dockerには
--pararel
で並列ビルドが可能になりますが、BuildKitでは標準的に並列ビルドを行います。そして、実行時間に差があり、Docker:並列buildオプション速度比較に記載されている通り、BuildKitの方が実行時間が短いです。 - 環境変数でDOCKER_BUILDKITを有効にする事で
docker build
した際にBuildKitが有効になります。 - Docker Composeでは、
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build
として利用します。しかし、今回はdocker build
を利用するため、Docker Composeは起動のみに留めてビルドには利用していません。 -
docker build
では--cache-from
によりイメージをキャッシュとして利用できますが、BuildKitはdocker build
の際のキャッシュとは別の場所にキャッシュを保存します。そのため、BUILDKIT_INLINE_CACHEを有効にして、BuildKitのキャッシュを利用できるようにする必要があります。
variables:
DOCKER_BUILDKIT: 1 # BuildKitを有効化
COMPOSE_DOCKER_CLI_BUILD: 1 # docker-composeでDocker cliを利用
build-app:
stage: build
script:
# Use Docker Layer Cache (DLC) to reduce build execution time
- docker pull $CI_REGISTRY_IMAGE/app:latest || true
# Use BuildKit to build fast
- docker build
--cache-from $CI_REGISTRY_IMAGE/app:latest
--tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE/app:latest
--build-arg BUILDKIT_INLINE_CACHE=1
./app # --build-argでBuildKitのキャッシュを利用
Dockerfileの改善
[改善前の参考実装] イメージサイズ(1.61 GB) CI実行時間(00:04:51)
現状では、Dockerfileの改善がされておらず、BuildKitを利用したことによる実行時間の改善が見られません。しかし、以降で説明するマルチステージビルドと併用する事で真価を発揮します。また、キャッシュが効きにくいシチュエーションでも同様です。まずは、CIパイプラインの課題で挙げられたdocker pull/push
の時間が無視できなくなってきている問題に取り掛かります。
4.AlpineベースのRubyイメージを利用して、軽量化
[参考実装] イメージサイズ(857.22 MB) CI実行時間(00:04:04)
ご存じの通りAlpine Linuxは、イメージサイズがUbuntuと比べても小さいです。これは、最小限の機能のみに制限されていることを意味しますので、必要なパッケージは各自で追加しなければいけません。そして、Alpine Linuxをそのまま利用すると、イメージのサイズを必要最小限に留める事はできますが、Dockerfileは複雑になり、Ruby on Railsを立ち上げるだけでも一苦労します。そこで、AlpineベースのRubyイメージを利用して、効率的に改善し、素のAlpine Linuxを使った場合とそん色のないパフォーマンスを出していきます。
ポイント:
- Alpine LinuxはBashではなくAshが利用されます。Dockerfile内で
ENTRYPOINT ["entrypoint.sh"]
を設定している場合、スクリプトの指定がBashになっていてビルドが通らないなんてことがあります。 -
apk add
でパッケージの追加を行いますが、追加が必要なパッケージの選定が難しいです。はじめは、ほかの人のブログ等を参考に追加してみることをお勧めします。そして、パッケージを厳選する作業は、マルチステージビルドを追加してから実施する方が効率的です。 -
apk add
の--vertual
を使う事で追加するパッケージ類に仮想の名前を定義でき、apk del
で定義した名前を指定することでパッケージを一括削除することが出来ます。不要なパッケージは削除し、イメージのサイズを抑える様にしましょう。 - Alpineベースのイメージにしただけでイメージサイズは半減し、約50秒程度の時間短縮となっています。それだけ
docker pull/push
に時間がかかっていたと言えます。イメージサイズを意識してDockerfileを作りましょう。
FROM ruby:2.6.5-alpine3.11
LABEL maintainer="Tomoyuki Sugiyiama"
ENV RUNTIME_PACKAGES="linux-headers libxml2-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql yarn"\
DEV_PACKAGES="build-base" # 必要なパッケージを選択
WORKDIR /make-it-quick
RUN apk add --update --no-cache $RUNTIME_PACKAGES
COPY Gemfile /make-it-quick
COPY Gemfile.lock /make-it-quick
RUN apk add --virtual build-dependencies --no-cache $DEV_PACKAGES\
&& bundle install\
&& apk del build-dependencies # build-dependenciesを削除
(略)
5.マルチステージビルドを利用して、軽量化と並列化
[参考実装] イメージサイズ(568.92 MB) CI実行時間(00:03:54)
マルチステージビルドは、複数のFROM文を定義することができ、それぞれ別のイメージとなります。そしてイメージ内で生成された内容を選び、もう一方のイメージにコピーする事が出来ます。コピーには、COPY --from=定義したイメージ名
と記載する事で、別のイメージからコピーします。最終的なイメージは、指定が無い限り最後のFrom以降のビルド結果がイメージとして生成され、それ以外のイメージは最終的なイメージに含まれません。この仕組みにより、本当に必要なファイルのみをイメージに追加することができ、イメージサイズを抑えることが出来ます。さらに、BuildKitの並列ビルドを利用する事で、複数のFrom文を並列に実行することが出来ます。
ポイント:
-
Bundle install
の時間が長いので、その間にyarn install
も実施しています。最終成果物(以降base-devとする)のイメージについてもCOPY -from
の直前まで並列でビルドされます。 - base-devにyarnやnodeコマンドが必要な場合は、nodeイメージのbuild-jsからコピーします。base-devのビルド時に
apk add yarn
を追加しても良いですが、yarnを追加した際に一緒にインストールされたnodeのバージョンが12.xであり、yarn install
に使用したnodeのバージョンが14.xであることから、実行時のデバッグコンソールにエラーが出力されましたので、正しいバージョンを指定して入れるようにして下さい。 - マルチステージビルドに変更する前の、コマンドやパッケージ類のバージョンから変えてしまわないように最新の注意を払いましょう。バージョン違いで余計な手間が発生します。
# Bundle install
# build-gemと定義
FROM ruby:2.6.5-alpine3.11 as build-gem
# (略)
# Yarn install
# build-jsと定義
FROM node:14.15.5-alpine3.11 as build-js
# (略)
# Create base image for develop
# 最終成果物(base-deb)
FROM ruby:2.6.5-alpine3.11 as base-dev
# (略)
# build-gemからビルドが終わったbundleを取得
COPY --from=build-gem /usr/local/bundle /usr/local/bundle
# build-jsからビルドが終わったnode_modulesを取得
COPY --from=build-js /build-js/node_modules /make-it-quick/node_modules
## Get node & yarn packages from build-js
COPY --from=build-js /usr/local/bin/node /usr/local/bin/node
COPY --from=build-js /usr/local/include/node /usr/local/include/node
COPY --from=build-js /opt/yarn-v1.22.5/bin/yarn /opt/yarn-v1.22.5/bin/yarn
COPY --from=build-js /opt/yarn-v1.22.5/bin/yarn.js /opt/yarn-v1.22.5/bin/yarn.js
COPY --from=build-js /opt/yarn-v1.22.5/lib/cli.js /opt/yarn-v1.22.5/lib/cli.js
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs && \
ln -s /opt/yarn-v1.22.5/bin/yarn /usr/local/bin/yarn
最終的な細かいポイント:
- 最後のbase-devと、base-devに受け渡すbundleやnode_modules以外は、イメージのサイズに影響しませんので、build-gemやbuild-jsの関係のないところで不要なファイルを削除してもCIの時間が伸びてしまうだけなので気をつけてください。
-
apk add
で追加したapline-sdkにはgccやmakeなどが詰め込まれているため、パッケージの追加に結構時間がかかってしまいますが、Dockerfileの可読性維持を優先します。ここのパッケージ選択にこだわっても、多少の時間削減にしかなりません。(最終的なイメージ作成には関係なく、イメージサイズに影響が無いため) -
bundle install
やyarn install
の結果からキャッシュや不要なものはオプションで削除しておきます。 -
bundle install
は-j4
オプションをつけて、4並列で実行しています。
# Bundle install
FROM ruby:2.6.5-alpine3.11 as build-gem
RUN apk --update --no-cache add alpine-sdk postgresql-dev # ビルドに必要なツール類を追加
WORKDIR /build-gem
COPY ["Gemfile", "Gemfile.lock", "./"]
RUN gem install bundler --version 1.17.2 && \
bundle install --without test -j4 # 不要ファイルの削除と並列実行
# Yarn install
FROM node:14.15.5-alpine3.11 as build-js
WORKDIR /build-js
COPY ["package.json", "yarn.lock", "./"]
RUN yarn install && yarn cache clean # 不要ファイルの削除
# Create base image for develop
FROM ruby:2.6.5-alpine3.11 as base-dev
RUN apk --update --no-cache add shadow sudo busybox-suid execline tzdata postgresql-dev
(略)
最後に
普段、Dockerはよく使いますが、DockerのCIは初めて実施しました。CIを実施する事で今まで知らなかったDockerに関する知識を得る事が出来ました。良い経験となりましたので、皆さまもぜひトライしてみてください。
最終成果物
- .gitlab-ci.yml
image: docker:latest
services:
- docker:dind
variables:
DOCKERIZE_VERSION: v0.6.1
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
before_script:
- docker -v
- apk update
- apk upgrade
- apk add docker-compose
- apk add --no-cache openssl
&& wget https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz
&& tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz
&& rm dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz
# Use gitlab docker registry
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-app:
stage: build
script:
# Use Docker Layer Cache (DLC) to reduce build execution time
- docker pull $CI_REGISTRY_IMAGE/app:latest || true
# Use BuildKit to build fast
- docker build
--cache-from $CI_REGISTRY_IMAGE/app:latest
--tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE/app:latest
--build-arg BUILDKIT_INLINE_CACHE=1
./app
- docker push $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE/app:latest
build-db:
stage: build
script:
# Use Docker Layer Cache (DLC) to reduce build execution time
- docker pull $CI_REGISTRY_IMAGE/db:latest || true
# Use BuildKit to build fast
- docker build
--cache-from $CI_REGISTRY_IMAGE/db:latest
--tag $CI_REGISTRY_IMAGE/db:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE/db:latest
--build-arg BUILDKIT_INLINE_CACHE=1
./db
- docker push $CI_REGISTRY_IMAGE/db:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE/db:latest
build-web:
stage: build
script:
# Use Docker Layer Cache (DLC) to reduce build execution time
- docker pull $CI_REGISTRY_IMAGE/web:latest || true
# Use BuildKit to build fast
- docker build
--cache-from $CI_REGISTRY_IMAGE/web:latest
--tag $CI_REGISTRY_IMAGE/web:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE/web:latest
--build-arg BUILDKIT_INLINE_CACHE=1
./web
- docker push $CI_REGISTRY_IMAGE/web:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE/web:latest
test:
stage: test
script:
# Pulling docker images from gitlab docker registry
- docker pull $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA || true
- docker pull $CI_REGISTRY_IMAGE/db:$CI_COMMIT_SHA || true
- docker pull $CI_REGISTRY_IMAGE/web:$CI_COMMIT_SHA || true
# These images are pre-built at build stages
- docker build
--cache-from $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
--tag make-it-quick_app
--build-arg BUILDKIT_INLINE_CACHE=1
./app
- docker build
--cache-from $CI_REGISTRY_IMAGE/db:$CI_COMMIT_SHA
--tag make-it-quick_db
--build-arg BUILDKIT_INLINE_CACHE=1
./db
- docker build
--cache-from $CI_REGISTRY_IMAGE/web:$CI_COMMIT_SHA
--tag make-it-quick_web
--build-arg BUILDKIT_INLINE_CACHE=1
./web
# Use the Docker CLI when executing a build
- docker-compose up -d
# Wait for docker-compose up
- dockerize -wait tcp://docker:80 -timeout 1m
- docker-compose run --rm app rake db:create
- docker-compose run --rm app rails db:migrate
- docker-compose run --rm app yarn test
- Dockerfile
# Bundle install
FROM ruby:2.6.5-alpine3.11 as build-gem
RUN apk --update --no-cache add alpine-sdk postgresql-dev
WORKDIR /build-gem
COPY ["Gemfile", "Gemfile.lock", "./"]
RUN gem install bundler --version 1.17.2 && \
bundle install --without test -j4
# Yarn install
FROM node:14.15.5-alpine3.11 as build-js
WORKDIR /build-js
COPY ["package.json", "yarn.lock", "./"]
RUN yarn install && yarn cache clean
# Create base image for develop
FROM ruby:2.6.5-alpine3.11 as base-dev
LABEL maintainer="Tomoyuki Sugiyiama"
RUN apk --update --no-cache add shadow sudo busybox-suid execline tzdata postgresql-dev
WORKDIR /make-it-quick
COPY --from=build-gem /usr/local/bundle /usr/local/bundle
COPY --from=build-js /build-js/node_modules /make-it-quick/node_modules
## Get node & yarn packages from build-js
COPY --from=build-js /usr/local/bin/node /usr/local/bin/node
COPY --from=build-js /usr/local/include/node /usr/local/include/node
COPY --from=build-js /opt/yarn-v1.22.5/bin/yarn /opt/yarn-v1.22.5/bin/yarn
COPY --from=build-js /opt/yarn-v1.22.5/bin/yarn.js /opt/yarn-v1.22.5/bin/yarn.js
COPY --from=build-js /opt/yarn-v1.22.5/lib/cli.js /opt/yarn-v1.22.5/lib/cli.js
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs && \
ln -s /opt/yarn-v1.22.5/bin/yarn /usr/local/bin/yarn
COPY . /make-it-quick
## Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
RUN mkdir -p /make-it-quick/tmp/sockets
CMD bundle exec puma -C config/puma.rb
おまけ
最後までお付き合いいただいた方へ、僕からのささやかのプレゼント(お得情報)です。
- イメージサイズを極限まで押さえたい方は、duしていきましょう。nodeコマンドって72Mもあるんですね。
/make-it-quick # du / -hd 1
92.0K /var
0 /dev
359.8M /usr
5.0M /lib
0 /proc
0 /sys
16.0K /media
4.0K /mnt
1.5M /etc
8.0K /run
56.0K /tmp
5.1M /opt
4.0K /home
8.0K /root
4.0K /srv
392.0K /sbin
1.9M /bin
299.9M /make-it-quick
673.7M /
/usr/local # du ./ -hd 2
8.0K ./lib/pkgconfig
25.6M ./lib/ruby
34.5M ./lib
16.0K ./share/.cache
4.0K ./share/ca-certificates
220.0K ./share/man
244.0K ./share
72.3M ./bin
924.0K ./include/ruby-2.6.0
7.0M ./include/node
7.9M ./include
288.0K ./bundle/specifications
188.6M ./bundle/gems
15.4M ./bundle/cache
4.0K ./bundle/doc
8.4M ./bundle/extensions
4.0K ./bundle/build_info
64.0K ./bundle/bin
212.8M ./bundle
8.0K ./etc
327.7M ./
/usr/local/bin # ls
bundle bundler erb gem irb node nodejs rake rdoc ri ruby yarn
/usr/local/bin # du ./ -hd 1
72.3M ./
/usr/local/bin # du ./node -hd 1
72.1M ./node
- Dockerfileの可視化ツールを利用して、分析しつつ楽しみましょう。