docker-composeを使ってReact+phoenix+postgresの環境を構築する

概要

ほとんど個人的メモです
自身でよく構築することの多い以下の環境を毎度、調べながら作るのが面倒なのでまとめたって感じです

  • React(front end)
  • phoenix(backend, elixir web framework)
  • postgres(open source database)

毎度悩むのが、dockerのimageをpullしてきたけど、どの場所、どのタイミングでcreate-react-appなりmix phx.new ...を実行するかなといったところ
個人的なベストプラクティスとしてはlocal環境で作成して依存環境ガァ^〜で何度も怒られてつらい思い出があるので
作成したcontainerの内部で上記のコマンドを実行してプロジェクトを作成し
生成されたプロジェクトをcontainerからlocal環境にコピーしてくるという方針を採用してみた

プロジェクトの生成環境が実行環境と同じになるというメリットがあるが、準備に多少の手間が発生する
しかしながら、やることは毎度同じなので大した苦労ではないなーと感じている(まだ2回しか作ってないけど)

やってることは色んな記事からアイディアをパクってまとめただけです
記事の一番最後にreferenceをまとめているのでご参照下さい

共通作業

pathはどこでも構わないので適当にdocker-composeをwrapするディレクトリを生成しておきます

$ mkdir react-phoenix-postgres
$ cd react-phoenix-postgres

以下の作業は全て、このディレクトリ内で行います
ちなみにDockerとdocker-composeについてはinstall済みという前提で進めていますのでお願いします

React環境の構築とcreate-react-appの実行

まずはReactのための環境から。node imageをbaseにした環境を用意して、そこでReactのプロジェクトを作成します
さっそくReactのためのディレクトリとDockerfileを生成

$ mkdir frontend
$ cd frontend
$ touch Dockerfile

続いて最低限の設定をDockerfileに記述

FROM node:12.2.0-alpine

RUN mkdir /app
WORKDIR /app

# nodeとnpmがinstallされているか確認用。省略しても問題なし
RUN node -v && npm --version

このDockerfileを使ってcontainerを作成します
-t というオプションの後に記述されているのは割り当てるタグと呼ばれるものです
後にタグを使ってコマンドを実行するので視認性のあるものにしておくと良いかと思います

$ docker build -t react_test:dev .

このコマンドを実行することで手元のDockerfileを元にcontainerが作成されます
メッセージログが流れてしまったので恐縮ですが

RUN node -v && npm --version
12.2.0
6.xx

Successfully built **containerID**

のようなメッセージが出ていればnode環境の作成は完了です
次にcreate-react-appを実行するための用意をします

create-react-appのコマンドは一度しか使用しないので、dockerのcontainerにaccessして、直接installします まずはcontainerにaccessするためにdockerのcontainerを立ち上げます
以下オプションの詳細ですが、無視してコマンドを打って頂いて構いません。一応書いておきました

  • d -> backgroundでdocker daemonを実行
  • t -> tty true -> docker containerを立ち上げたままにしておく
  • p -> spec port -> 立ち上げるportとexposeするportの指定
  • name -> containerに名前を割り当てる(タグとは別で実行中のみ有効と判断しています)
  • react_test:dev 先ほど付与したタグ名

cotainerを立ち上げます

$ docker run -dt --name react_train -p 3000:3000 react_test:dev
# cotainerIDが表示され、terminalが入力可能状態になれば成功

よくあるミスとしては以下が候補に上がります

  • すでにportが使用されている(別のportにするか重複元の環境を停止させる)
  • 同盟の--nameが割り当てられたcontainerがすでにUPされている(docker stop containerIDで停止させる)

問題なく進められていれば先ほど立ち上げたcontainerにashを使ってアクセスします
なんでash?ってなりますけど、alpineではshellにashが使われているそうで、bashやらにすると怒られます

$ docker exec -it react_train ash

無事にアクセスができるとroot#containerIDのようなプロンプトが立ち上がります
まずは適当にnode.jsとnpmのversionでも確認しておきます

# dockerのcontainer内部
node -v && npm --version
12.2.0
6.x.x

無事にinstallされているようなので、次にcreate-react-appコマンドが使いたいのでnpm経由でinstallします
ここは-gをつけてglobal installにしておいてください。package.jsonファイルが現状のディレクトリにないので-gをつけないと怒られます
ついでにcreate-react-appも実行します

$ npm i -g create-react-app && create-react-app app-name

Happy Hacking!と準備できたからserver立てれるよって文言が表示されていればOKです
これでプロジェクトの生成は完了です。あとはこの作成したプロジェクトをlocal環境にcopyしてあげるのみ

exitと入力し現在、アクセス中のcontainerから離脱します
続いて、以下コマンドを使ってlocal環境にプロジェクトをcopyします

# 現在のカレントディレクトリにプロジェクトをcopy
$ sudo docker cp react_train:/app/app-name .

これでlocal環境に生成したプロジェクトがcopyされた
しかしながらcopyされたプロジェクトの権限をlコマンドで確認してみるとオーナーがrootになっていることがある
そのためchownを使ってオーナーを変更しておきます(後にハマってだるいことになったので

sudo chown -R login-user-name app-name

再びlコマンドで確認してオーナーが自身のユーザーネームになっていればOKです
これでReactのプロジェクトの生成は完了です

phoenix(Elixir)環境の構築とmix phx.newでのプロジェクト作成

やることはReact環境を用意した時と全く同じです。まずはディレクトリとDockerfileを用意します

$ mkdir backend
$ cd backend
$ touch Dockerfile

やはり最低限の設定をDockerfileに記述

FROM elixir:alpine

WORKDIR /app

RUN yes | mix local.hex
RUN yes | mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
RUN mix local.rebar --force

# elixirがinstallされているかの確認用(省略可)
RUN elixir -v

先ほど同様に作業を進めていきます

$ docker build -t elixir_test:div .
$ docker run -dt --name elixir_train -p 8080:8080 elixir_test:dev
$ docker exec -it elixir_train ash

これでphoenixのプロジェクトの作成準備は整いましたので早速...

# dockerのcontainer内部(phoenixの生成に関するオプションは適宜、自身で付与してください(no-webpackを打ち忘れたorz))  
mix phx.new backend

これでphoenixのプロジェクトが生成されたので同様にlocal環境にcopyします

$ sudo docker cp elixir_train:/app/backend .

phoenixのプロジェクトの生成は以上ですが、database設定を行う必要があるためdocker-composeの設定と共に行なっていきます

docker-compose.ymlの作成とdatabaseの設定

これまで行なってきたdocker runをdocker-composeを使ってまとめてできるようにしてあげます
記述量は若干ありますが、どれも1つずつ見るとやっていることは大したことではないのでご心配なく
レゴを積み上げるようなノリで記述出来ます

version: "3"

services:

  # reactへのsetting
  frontend:
    build:
      context: test # buildしたいDockerfile(node)が格納されているディレクトリ
      dockerfile: Dockerfile
    container_name: react_frontend # cotainerに命名
    volumes:
      - "./test:/app/test" # 先ほど作成したreactプロジェクトのディレクトリ名
    ports:
      - "3001:3000" # 内部ポートと外部ポートをexpose
    environment: 
      - NODE_ENV=development
    tty: true # 起動を継続するように指定
    working_dir: "/app/test" # マウントしたプロジェクトのディレクトリを指定(package.jsonがあるため)
    command: "npm run start" # 起動時に実行させるcommand

  # phoenixへのsetting(ほとんどReactと同じ)
  backend:
    build:
      context: elixir
      dockerfile: Dockerfile
    volumes:
      - "./elixir/backend:/app/backend"
    container_name: elixir_backend
    ports:
      - "8080:4000"
    tty: true
    working_dir: "/app/backend"
    command: ash -c "mix deps.get && mix phx.server"
    depends_on: # databaseを使いたいので下記記述のdbに連携しておく
      - db

  # postgresへのsetting
  db:
    image: postgres:12.0 # ymlの中でimageを指定
    container_name: postgres_for_phoenix
    tty: true
    volumes:
      - "./postgres/lib:/var/lib/postgres"
    # postgres用の環境変数をset
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=root_password
      - POSTGRES_INITDB_ARGS="--encoding=UTF-8"
      - POSTGRES_HOST=db
    ports:
      - "5432:5432"

やっていることはかなり共通事項が多いので特に難しくないです
インフラを構築する才能がないねーと言われた僕ですら書けるのがdocker-composeです。最高か

postgresのマウント用に指定したディレクトリが現在ない状態ですので、作っておきます

$ mkdir -p mysql/lib

phoenixのdatabase設定

configディレクトリ内部のdev.exsの最下部を編集します
先ほどdocker-compose.ymlに環境変数として用意したものに値を変更します

./project-name/config/dev.exs

config :backend, Backend.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "root", # POSTGRES_USER
  password: "root_password", # POSTGRES_PASSWORD
  database: "backend_dev", # ecto.createコマンドを使わないので変更しても良いし、しなくても良い
  hostname: "db", # service名
  pool_size: 10

準備も完了したのでcontainer達を立ち上げてみます

$ docker-compose up --build

frontendとdatabaseは特に問題なく立ち上がるかと思いますが、backendのcontainerはdatabase is not exist的なmessageを出し続けているかと思います
なので接続するためのdatabaseを作ってあげましょう

先ほどdocker-compose up --buildでcontainerを立ち上げているので、postgresのimageをbuildして生成されたcontainerが立ち上がっているはずです
docker psで確認します

$ docker ps
9f676931727a        postgres:12.0          "docker-entrypoint.s…"   2 days ago          Up 49 seconds       0.0.0.0:5432->5432/tcp   postgres_for_phoenix

立ち上がっているのが確認できたら、このcontainerにアクセスして、内部にdatabaseを作成します
postgresのプロンプトを立ち上げる

$ docker exec -it container-id /bin/bash
psql
root=#

先ほど./project-name/config/dev.exsに指定した名前のdatabaseを作成します

root=# create database backend_dev;
CREATE DATABASE

lで作成したdatabaseが存在していることを確認できたらdatabaseの作成は完了です
これで全作業が終了

Reactとphoenixのそれぞれのプロジェクトに対してdocker-compsoe.ymlで設定したportにアクセスしてみて下さい

でそれぞれ、welcomeページが表示されていれば成功です(phx->no-webpackを指定し忘れてcssが読み込まれていない)
f:id:takamizawa46:20191007222214p:plain
f:id:takamizawa46:20191007222301p:plain

docker psで確認すると3つのcontainerが立ち上がっているはずです

$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                    NAMES
a9457b933808        react_train_frontend   "npm run start"          52 seconds ago      Up 49 seconds       0.0.0.0:3001->3000/tcp   react_frontend
a80a6a939350        react_train_backend    "ash -c 'mix deps.ge…"   2 days ago          Up 47 seconds       0.0.0.0:8080->4000/tcp   elixir_backend
9f676931727a        postgres:12.0          "docker-entrypoint.s…"   2 days ago          Up 49 seconds       0.0.0.0:5432->5432/tcp   postgres_for_phoenix

containerを終了したい時はdocker-compose downとすればOKです

参考文献