フロントエンドエンジニアでも怖くないReact+Docker構築

March 29th, 2020

Introduction

ちょっと前ですが、Node12がLTSになりましたね。 喜ばしいことではあるんですが、実はちょっと前にMacのCatalinaとNode12の掛け合わせで、Node gypが動かずハマってちょっと苦労したことがありまして。 Dockerで動くようちゃんと構築しておけば無駄に苦労せずに済んだのになーっと当時は思ったんですが、そこまで時間かけたくなかったのでその場しのぎで対応したりしてました。

その後困ったことはないんですが、必要になってからやってみても遅いなと思い、本サイトでDocker環境を構築してみる事にしました。 他の記事でも書いてますが、本サイトは

  • GatsbyJS
  • Storybook
  • Jest

を使用して開発しています。 なので今回はこの3つをDocker上で動かせるようにしてみたいと思います。

環境情報

  • Mac OS Mojave(10.14.5)
  • Docker: 19.03.8
  • Node: 12.14.1

npm script(一部抜粋)

{
  "scripts": {
    "start": "gatsby develop & npm run storybook",
    "dev": "gatsby develop",
    "build": "gatsby build",
    "storybook": "start-storybook -p 6006",
    "storybook:docker": "start-storybook -p 6006 --ci --quiet",
    "test": "jest",
  }
}

Docker準備

まずはDockerをインストール。 https://hub.docker.com/editions/community/docker-ce-desktop-mac

インストールが終わったらアプリケーション開いて、ターミナルからバージョン確認できればとりあえず準備OK。

docker -v

Docker基礎知識

DockerにはDocker Engineとかイメージとかコンテナとか、Dockerを構築するいろいろがあるんですが、この辺をぼくなりの理解で解説していこうと思います。(誤りがあったらすいません、、、)

Docker Engine

Macで常駐するDockerをうごかすためのプログラムです。 Dockerのワークフロー管理はこのEngineがいい感じにやってくれます。 Docker fot Macとかいれたら、あとはこれを気にすることはほぼないです。

イメージ

基本的に実行環境とは、「Linux上でNodeの12.14.1でReactが16で・・・」みたいに階層別に分けられますよね。 このようなあるDocker環境を構築するのに必要になる階層的な土台1つ1つをDockerイメージと呼びます。

Dockerfile

Dockerイメージを構築する上で必要な設定情報を記述するのがこのDockerfileです。 「Nodeのバージョンは幾つで、コマンドは何をしておけばよくて、コンテナ実行時のポートやコマンドは・・・」といった感じで環境に必要な情報を記述していきます。

コンテナ

コンテナはアプリケーション実行環境です。 イメージを元に作成した環境上でアプリケーションソースを実行することができます。 基本的にこれが1開発環境だと思っていいかと思います。

volumes

ボリュームはデータやファイルの永続化をおこなう場所です。 node_modulesなど、ローカルのリポジトリ上になくてもコンテナ上にはあってほしいものなどを記憶するのに使います。

docker-compose

docker-composeはコンテナを複数立ち上げる時や冗長な起動コマンドを書かなくて済むようにする為のツールです。 Docker for Macで同梱されており、コンテナの起動時の設定をdocker-compose.ymlに記述します。

React(Gatsby)をDockerで動かす

docker設定周り

では早速アプリケーションの実行環境を構築していきたいと思います。 まずはGatsbyアプリケーションのルートにDockerfileを作成し、以下を記述します。

FROM node:12.14.1

WORKDIR /app
COPY package*.json ./
RUN npm cache clean --force && npm ci
COPY . .

順番に説明すると、

  1. node12のイメージの上に構築
  2. コンテナ上にappディレクトリを作成し、その中で作業
  3. ローカルにあるpackage.jsonpackage-lock.jsonをコピーして配置
  4. ビルド時のみのコマンドを実行
  5. COPY . . でアプリケーショソースをバンドル・コピー

という流れになっています。 ただし、ここで感の言い方はお気づきでしょうがこれだとローカルにあるnode_modulesなどもコンテナにコピーされてしまうのです。 こういった場合のために、アプリケーションのコピーから除外する為の設定として、.dockerignore 作成し、コピーする必要のないディレクトリを設定します。

node_modules/
public/
.cache/
dist/

次にdocker-compose.ymlを書いていきます。

version: '3'
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: "npm run dev -- --host 0.0.0.0"
    ports:
      - "8000:8000"
    volumes:
      - /app/node_modules
      - .:/app
    environment:
      - NODE_ENV=development

ここで重要なのはgatsbyの場合、0.0.0.0 を指定しないとlocalhostでアクセスできないこと、portの指定をしないといけないこと、そしてvolumesの設定をしないといけないことです。

これで設定系の準備は完了です。

コンテナを起動する

ではいよいよコンテナを起動したいと思います。 Dockerfileで書いた内容をまずはbuildします。

docker-compose build

buildは初回は時間がかかるので、気長に待ってください。 終わったらupコマンドでコンテナを起動してみましょう。

docker-compose up

gatsbyのコマンドログが流れて、localhost:8000にアクセスできたらこれで完了です。 わかってしまえば割とすんなりコンテナ構築までいけました。 (途中volumesらへんで詰まって僕は時間かかりましたがww)

Storybookのコンテナも作ってみる

storybookを運用している方も多いと思うので、同じDockerfileでstorybookのコンテナも作ってみましょう。 (さっき立てたコンテナで作ったGatsbyの環境とport違うので同じservice内にstorybookも立てられると思うんですが、それだとstorybookのビルドログとgatsbyのビルドログが混じってわかりづらくなりそうだったので別で立てることにしました。)

さっき作成したDockerfileに以下を追記して、build+upしてみてください。

  storybook:
    build:
      context: .
      dockerfile: Dockerfile
    command: "npm run storybook:docker"
    ports:
      - "6006:6006"
    volumes:
      - /app/node_modules
      - .:/app
    environment:
      - NODE_ENV=development

localhost:6006でstorybookが見れました。 application側と同じような設定が多いので、共通設定にできないかいろいろやってみたのですがうまくいきませんでした。 Dockerfileのv2だったらextendsが使えるのですが、カオスになりやすい為なのかv3からなくなっちゃってて代替案はあんまライトなのはなかったので諦めました。

Jestも回しながら開発したい場合は?

このサイトはJestでwatchしながら開発してるので、Jestもコンテナ上で回せないと困ります。 ということで、もう一個コンテナを、、、 と思ったんですが、別にコンテナを立てなくてもコンテナに入れればよくね?と思いJest用にコンテナを立てるのはやめました。

以下のコマンドでGatsby用のコンテナへ入れるので、そこでテスト回せばOKです。

docker-compose exec web bash
npm t

まとめ

ざっとですがDockerでGatsby開発環境を構築できました。 チーム開発してると環境構築でDockerとかVagrantないと結構時間取られるので、やはりこういった仮想系の環境構築は偉大ですね。 しかもDocker思いの外シンプルで結構わかりやすい。 もっといろいろ詰まるかと思ってたんですが、どうやら食わず嫌いだったみたいです。 (とかいってると本職な方々からえらいつっこみくらうかもしれないですけど・・・)

今回はそんな感じで。