RareJob Tech Blog

レアジョブのエンジニア・デザイナーによる技術ブログです

マルチステージビルドで環境毎のLaravelイメージを作る

はじめに

こんにちは、サービス開発チームの加々美です。初投稿になります。

先日直属の上司の方に「最近2日に1回はカレー食べてます」と謎の共有をしたところ、「疲れてるんじゃないですか?」と言われました。 私はただカレーにハマってるだけだと思っていますが、もし週に何回もカレーを食べてしまうという方は注意が必要かもしれません。

早速ですが本題に入っていきたいと思います。

Dockerイメージを作る際に開発環境と本番環境でインストールしたいパッケージや設定が異なることがあると思います。

一方で全く異なるかというとそんなことはなく、基本的には同じで開発時のみデバッグツール(PHPだとXdebug)を利用できるようにしたい、といったケースが多いです。

今回はLaravelの環境毎に利用できるDockerイメージ作成時の知見についてまとめた内容になります。

マルチステージビルドに関する記事は多いですが、意外とLaravelでの記事がなかったためこの機会にまとめました。

今回のフォルダ構成

今回のサンプルは以下のフォルダ構成で作成しています。 srcにはLaravelアプリケーションのファイルを置いてます。

├── docker
│   ├── app
│   │   ├── Dockerfile
│   │   └── docker-php-ext-xdebug.ini
│   └── web
│       ├── Dockerfile
│       └── default.conf
├── docker-compose.yml
└── src

マルチステージビルドとは

Docker 17.05から利用できるようになった機能で、マルチステージビルドが登場する前は次に説明するDockerfileを複数作成するなど共通の定義を利用できませんでした。 マルチステージビルドを利用することでベースとなるイメージを作成し、それを再利用することによりイメージ毎に不要なファイルが含まれることを防ぐことができイメージサイズの削減が可能になります。

matsuand.github.io

マルチステージビルドを使用しない場合

マルチビルドステージを利用しない場合はDockerfileを複数作成する方法がありますが、Dockerfile毎にそれぞれで定義する必要があるため共通で使用する箇所が多い場合でも全て記載しないといけません。

Dockerfileを別々で定義する場合、変更する度にそれぞれに対して変更を加える必要があり管理が煩雑になります。

開発用のイメージのビルド

何も考えずにdocker/app/Dockerfileを作成すると以下になります。

FROM composer:latest AS composer

FROM php:7.4-fpm-alpine

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN set -eux \
  && apk update \
  && apk --no-cache add \
    git \
    oniguruma-dev \
    libzip-dev \
    zip \
  && docker-php-ext-configure zip \
  && docker-php-ext-install pdo_mysql mbstring zip \
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo \
  && apk --no-cache add \
    autoconf \
    gcc \
    g++ \
    make \
    openssh-client \
  && pecl install xdebug \
  && docker-php-ext-enable xdebug

COPY ./src /var/www/

上記の中で開発環境用に追加するパッケージはこちらです。

  && apk --no-cache add \
    autoconf \
    gcc \
    g++ \
    make \
    openssh-client \
  && pecl install xdebug \
  && docker-php-ext-enable xdebug

RUN内で2度apk --no-cache addを行なっていますが、本番用のイメージと比較しやすいようにあえてこのように記述しています。

このDockerfileをビルドしてみます。

docker build . -f docker/app/Dockerfile -t sample1

docker imagesで作成されたイメージを確認してみます。

REPOSITORY     TAG       IMAGE ID          CREATED            SIZE
sample1        latest    536e1516fffb      2 hours ago        378MB

本番用のイメージのビルド

ビルドするファイルの内容は以下になります。

FROM composer:latest AS composer

FROM php:7.4-fpm-alpine AS builder

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN set -eux \
  && apk update \
  && apk --no-cache add \
    git \
    oniguruma-dev \
    libzip-dev \
    zip \
  && docker-php-ext-configure zip \
  && docker-php-ext-install pdo_mysql mbstring zip \
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo

COPY ./src /var/www/

こちらもdocker imagesで作成されたイメージを確認してみます。

REPOSITORY  TAG       IMAGE ID          CREATED           SIZE
sample2     latest    dbb97bb45402      9 seconds ago     136MB
sample1     latest    536e1516fffb      2 hours ago       378MB

Xdebug関連のインストールがないだけでイメージサイズが半分以下になっているのがわかりますね。

マルチステージビルドを使う

docker/app/Dockerfileは以下になります。

FROM composer:latest AS composer

FROM php:7.4-fpm-alpine AS builder

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN set -eux \
  && apk update \
  && apk --no-cache add \
    git \
    oniguruma-dev \
    libzip-dev \
    zip \
  && docker-php-ext-configure zip \
  && docker-php-ext-install pdo_mysql mbstring zip \
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo


FROM builder AS dev

RUN set -eux \
  && apk --no-cache add \
    autoconf \
    gcc \
    g++ \
    make \
    openssh-client \
  && pecl install xdebug \
  && docker-php-ext-enable xdebug

COPY ./src /var/www/

FROM builder AS prod

COPY ./src /var/www/

簡単に解説していきたいと思います。

FROM php:7.4-fpm-alpine AS builderの箇所で共通で利用できるステージを定義しています。

devprodのステージを定義する際にbuilderステージを利用します。

builderの利用方法はFROM builder AS devのように利用します。

devではデバッグ用のパッケージをインストールしていますが、prodではbuilderをそのまま利用しています。

ビルドする際は--target devのようにビルドしたいステージをオプションで指定します。

それぞれビルドします。

docker build . docker/php/Dockerfile -t sample3 --target dev

docker build . docker/php/Dockerfile -t sample4 --target prod

実行結果を確認すると以下のようになりました。

REPOSITORY    TAG       IMAGE ID          CREATED              SIZE
sample4      latest     39bd6a456c9d      29 seconds ago       136MB
sample3      latest     e1bee9d50280      About a minute ago   378MB

マルチステージビルドを利用しないパターンと同じイメージがビルドできました。

弊社では主にAWSを使用しており、ECSへの移行も考慮しているので各環境毎にビルドしたイメージをECRにpushする場合などは--targetでステージを指定することで対象のDockerイメージをビルドすれば良さそうです。

また、ステージを指定してビルドした場合は、対象ステージのみビルドされます。

つまり、prodを指定してビルドする際はdevはビルドされません。

docker-composeでステージを指定して利用する

最後にdocker-composeで指定する方法について解説したいと思います。

今回使用したdocker-compose.ymlはこちらでです。

version: '3.7'
services:
  app:
    build:
      context: .
      dockerfile: ./docker/app/Dockerfile
      target: dev
    volumes:
      - ./src:/var/www
      - ./docker/app/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
  web:
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    ports:
      - "8000:80"
    volumes:
      - ./src:/var/www
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf

ステージを指定している箇所はtarget: devの箇所です。

意外と今までやったことがなかったですが、簡単に指定できました。

あとはdocker-compose upを実行し、立ち上がったらdocker-compose exec app shでコンテナ内に入り、composer create-project laravel/laravel .を実行すればブラウザで確認できるようになります。

おまけ

サンプルのDockerfile内でcomposerに関して以下のように利用していました。

FROM composer:latest AS composer

COPY --from=composer /usr/bin/composer /usr/bin/composer

これは外部イメージをステージとして利用する方法です。

https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/multistage-build/#use-an-external-image-as-a-stage

検証できていないですが、この方法を使用すればbuilderで定義したイメージをCIでECRなどにあげておき、開発者はそこからステージとして開発環境に利用できるのかも?と思ったりしました。

まとめ

今回はマルチステージビルドを利用したLaravelのDockerイメージ作成について説明しました。

もっと良い使い方あるという方はコメント頂けると嬉しいです。

思ったより記事を書くのに時間がかかったので今夜はカレーを食べたいと思います。(実は5日連続。。)