LoginSignup
28
9

More than 1 year has passed since last update.

様々な開発マシンでもPhoenixが動くDockerfile/docker-composeの作り方

Last updated at Posted at 2021-12-05

この記事はElixir Advent Calendar 2021の6日目です。

昨日は @h3_poteto「ElixirでHot Code Reloadingするときに気をつけること」でした

ごあいさつ

こんにちは Koyo です!普段はコミュニティで Elixir 開発しつつ、仕事ではRuby on RailsでWebアプリケーションをごにょごにょ書いております。

さて会社外でプロジェクトに参加していると、開発マシンがMacの人もいればWSL2やLinuxもいたりとバラバラだったりしますよね。なのでDockerを用いて開発環境をなるべく統一しよう!・・・と思ったのですが、意外にハマりどころがあって苦戦したので書いてみました。

  • Mac
  • WSL2 / Linux
  • Mac / WSL2 / Linux 混在環境

の3パターンに分けて解説していきたいと思います!

なお説明で使うファイルはこちらにまとめております。

想定バージョン

- elixir 1.13
- phoenix 1.6.x

※20220417 最新版にアップデート

Docker Desktop for Mac で Phoenix が動く Dockerfile / docker-compose

こんな感じで動きます。今回はベースイメージはalpineにしてます!

Dockerfile
FROM elixir:1.13-alpine

RUN apk update && apk add \
  inotify-tools git build-base npm bash

RUN mix do local.hex --force, local.rebar --force, archive.install --force hex phx_new
docker-compose.yml
version: '3'
services:
  web:
    build:
      context: .
    command: mix phx.server
    environment:
      DB_HOST: db
    links:
      - db
    ports:
      - 4000:4000
    volumes:
      - .:/work
    working_dir: /work
  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_PASSWORD: postgres
    ports:
      - 5432:5432
    volumes:
      - dbdata:/var/lib/postgresql/data
volumes:
  dbdata:

こんな感じで新しいプロジェクトも作ることができます!

docker compose run --rm --no-deps web mix phx.new sample

※Docker経由でPhoenixを動作させるときの注意点

なお、実際に動作させる場合は Dockerfiledocker-compose.yml を作成したプロジェクトのディレクトリ(今回はsampleディレクトリ)にコピーするのを忘れずに。
さらに config/dev.exs の編集が必要です。
まず hostname を docker-compose.yml の環境変数で指定したホスト名にしてください。
また、ここがハマりポイントですが、デフォルトだと http: [ip: {127, 0, 0, 1}, port: 4000], になっているのを http: [ip: {0, 0, 0, 0}, port: 4000] に修正する必要があります。

config/dev.exs
config :sample, Sample.Repo,
  ...
  hostname: System.get_env("DB_HOST") || "localhost",

...

config :sample, SampleWeb.Endpoint,
  # Binding to loopback ipv4 address prevents access from other machines.
  # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
  http: [ip: {0, 0, 0, 0}, port: 4000],

Docker for Mac 以外(WSL2 / Linux など)でも動くように

上記でも Docker Desktop for Mac であれば root でコンテナを動かしてファイル生成しても、ホスト側のUID/GIDと一致するため問題ありません(ドキュメント探してもパッと見つからなかったので、理由分かる人いたら教えてくださいw)。

一方で他の環境(例えば Docker Desktop for Windows )で先ほどの mix phx.new コマンドを実行してみると以下のようになってしまいます。

$ ls -l
drwxr-xr-x 9 root root 4096 Dec  5 20:24 sample/

コンテナのユーザが root であるため、作成されたファイルももちろん root になってしまいます。
mix phx.new 済みのプロジェクトであっても mix deps.get などでコンテナ経由でファイル生成することはよくあるため、これだとパーミッション周りのエラーが頻発し、困ってしまいますね。

コンテナ内にホストと同一UID/GIDのユーザを作る

ということで、これを解決するために「コンテナ内にホストと同一UID/GIDのユーザを作りたい」と思います。
下準備として下記のようなMakefileを書いてみます。

Makefile
.PHONY: setup

setup:
	echo "host_user_name=${USER}" > .env
	echo "host_group_name=${USER}" >> .env
	echo "host_uid=`id -u`" >> .env
	echo "host_gid=`id -g`" >> .env

実行すると .env ファイルが生成されます。

$ make setup

さて、これを踏まえて下記のようにDockerfile/docker-composeを作成します。
環境変数をdocker-compose.ymlに渡し、Dockerfileへはビルド時の引数という形でホストのユーザ情報を渡します。そしてコンテナビルド時にホストと同じUID/GIDのユーザが作られるようにします。

Dockerfile
FROM elixir:1.13-alpine

RUN apk update && apk add \
  inotify-tools git build-base npm bash

ARG host_user_name
ARG host_group_name
ARG host_uid
ARG host_gid

RUN apk add shadow && groupadd -g $host_gid $host_group_name \
  && useradd -m -s /bin/bash -u $host_uid -g $host_gid $host_user_name

USER $host_user_name

RUN mix do local.hex --force, local.rebar --force, archive.install --force hex phx_new
docker-compose.yml
version: '3'
services:
  web:
    build:
      context: .
      args:
        - host_user_name=${host_user_name}
        - host_group_name=${host_group_name}
        - host_uid=${host_uid}
        - host_gid=${host_gid}
    command: mix phx.server
    environment:
      DB_HOST: db
    links:
      - db
    ports:
      - 4000:4000
    volumes:
      - .:/work
    working_dir: /work
  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_PASSWORD: postgres
    ports:
      - 5432:5432
    volumes:
      - dbdata:/var/lib/postgresql/data
volumes:
  dbdata:

さて、これで実行してみると今度はrootにならずにできていますね!

$ docker compose run --rm web mix phx.new sample
...
$ ls -l
drwxr-xr-x 9 koyo koyo 4096 Dec  5 20:52 sample/

Mac / WSL2 / Linux 混在環境でも動くように

これで解決・・・と思いきや Mac 環境で上記の Dockerfile をビルドしてみると問題が発生してしまいます。
どうも Mac 側の GID と衝突してしまうよう・・・(いい回避策知ってたら教えてくださいw)。

 > [3/4] RUN apk add shadow && groupadd -g 20 koyo   && useradd -m -s /bin/bash -u 504 -g 20 koyo:                                                                          
#6 0.388 (1/2) Installing linux-pam (1.5.1-r1)
#6 1.901 (2/2) Installing shadow (4.8.1-r0)
#6 1.962 Executing busybox-1.33.1-r6.trigger
#6 1.968 OK: 264 MiB in 55 packages
#6 2.008 groupadd: GID '20' already exists

プロジェクトがMacのみ!とかMac以外のみ!であれば、上記のどちらかを選択して使えばいいかと思います。
しかし実際には混在していることが多いのではないでしょうか。
筆者が参加したプロジェクトでは Mac / WSL2 / Linux と様々なOSで開発している方が集まっていました笑
ということで頑張ってみましょう!

…とその前に、 docker-compose.yml を上書きする手法と、マルチステージビルドについて説明します。

docker-compose の設定を上書きする

docker compose コマンドは標準で docker-compose.override.yml という上書き用のファイルをサポートしています。これが存在する場合 docker-compose.yml の設定を docker-compose.override.yml で上書きしてくれます。

マルチステージビルド

Dockerfile には実は「複数のFROM」を書くことができます。例えば1つ目のFROMフェーズでビルドを行い、そこで作成したビルド結果だけを後続のステージに渡すことで、コンテナ容量を削減できます。
今回はこちらをビルド処理の共通化に使ってみたいと思います。

Dockerfile
FROM ... AS base

FROM base AS ...

Mac / WSL2 / Linux 混在環境でも動く Dockerfile/docker-compose.yml

まず Dockerfile を以下のようにします。 base フェーズで共通の処理を行い、その後 setup_user フェーズを経由してユーザを作成する build_as_user と、経由せずユーザを作成しない build_as_root フェーズに分岐します。

Dockerfile
FROM elixir:1.13-alpine AS base

RUN apk update && apk add \
  inotify-tools git build-base npm bash

FROM base AS setup_user
ARG host_user_name
ARG host_group_name
ARG host_uid
ARG host_gid

RUN apk add shadow && groupadd -g $host_gid $host_group_name \
  && useradd -m -s /bin/bash -u $host_uid -g $host_gid $host_user_name

USER $host_user_name

FROM setup_user AS build_as_user
RUN mix do local.hex --force, local.rebar --force, archive.install --force hex phx_new

FROM base AS build_as_root
RUN mix do local.hex --force, local.rebar --force, archive.install --force hex phx_new

docker-compose.yml では target: build_as_root とすることでユーザを作成せずビルドが行われるようにしておきます。

docker-compose.yml
version: '3'
services:
  web:
    build:
      context: .
      target: build_as_root
    command: mix phx.server
    environment:
      DB_HOST: db
    links:
      - db
    ports:
      - 4000:4000
    volumes:
      - .:/work
    working_dir: /work
  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_PASSWORD: postgres
    ports:
      - 5432:5432
    volumes:
      - dbdata:/var/lib/postgresql/data
volumes:
  dbdata:

このままだと Docker Desktop for Mac 環境でしか動かないため、ここからさらに手を加えます。
まず docker-compose.yml とは別のディレクトリに以下の docker-compose.override.yml を配置します。
target: build_as_user とすることでユーザを作成してビルドが行われるようにしつつ、環境変数経由でホストのユーザ情報をDockerfileに渡します。

docker/docker-compose.override.yml
services:
  web:
    build:
      target: build_as_user
      args:
        - host_user_name=${host_user_name}
        - host_group_name=${host_group_name}
        - host_uid=${host_uid}
        - host_gid=${host_gid}

そして Makefile を以下のようにして、 docker/docker-compose.override.ymldocker-compose.yml があるディレクトリにコピーするようにしておきます。

Makefile
.PHONY: setup_for_docker_user

setup_for_docker_user:
	cp docker/docker-compose.override.yml .
	echo "host_user_name=${USER}" > .env
	echo "host_group_name=${USER}" >> .env
	echo "host_uid=`id -u`" >> .env
	echo "host_gid=`id -g`" >> .env

こうすることで 「Macユーザはそのまま、そうでない場合は make setup_for_docker_user を実行してもらう」 とすることで、異なるOSが混在する環境でも Elixir/Phoenix を動作させることができます!あとは README.md にでも書いておくといいですね。
便利!

まとめ

  • Mac
  • WSL2 / Linux
  • Mac / WSL2 / Linux 混在環境

で動く Dockerfile と docker-compose.yml を作ってみました。特に「Mac / WSL2 / Linux 混在環境」で動くようにするのにだいぶ苦労したので、ぜひ使ってください笑
今回のDockerfileは一例ですので、alpineを使わないようにしてみるなど、プロジェクトの構成に併せて適宜カスタマイズしてみてください!(alpine使わずに動かす Dockerfile 書いたらぜひ記事にして教えてください!)

今回紹介したDockerfile群はこちらのリポジトリで公開しています!
READMEにも動かし方を書いてあるので参考にしてみてください^w^

私の記事はここでおしまいです!

明日は、@kn339264の「Elixir/Phoenix入門者向けコミュニティpiyopiyo.exを立ち上げた話」です

ぜひぜひ読んでみてください!

28
9
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
9