はじめに
Docker コンテナ と Docker Compose を利用して、さまざまなマシンで Phoenix アプリを開発する方法について考えます。
@koyo-miyamura さんの記事
やり方や問題点については、@koyo-miyamura さんの記事に詳しく解説されています。
よって、題目の課題自体は同記事を読めば解決してしまいます。
本記事では、@koyo-miyamura さんの記事で未解決の部分に挑戦してみたいと思います。
未解決の部分
未解決の部分とは、具体的にはこれらの問題です。
問題1
これで解決・・・と思いきや Mac 環境で上記の
Dockerfile
をビルドしてみると問題が発生してしまいます。
どうも Mac 側の GID と衝突してしまうよう・・・(いい回避策知ってたら教えてくださいw)。
問題2
今回のDockerfileは一例ですので、alpineを使わないようにしてみるなど、プロジェクトの構成に併せて適宜カスタマイズしてみてください!(alpine使わずに動かす Dockerfile 書いたらぜひ記事にして教えてください!)
オープンソースソフトウェアのコミュニティではこういうのを見て見ぬ振りするのは好ましくありません。何らかの恩返しを試みます!
オープンソースソフトウェア
オープンソースソフトウェアの利用者は共同開発者のように扱われる。利用者はソフトウェアのソースコードにアクセスすることができ、ソフトウェアへの機能追加、ソースコードの修正、バグの報告、ドキュメントの提出が可能である。利用者はそれらをソフトウェア開発のメインストリームに反映することができるし、利用者が望むのであれば自身の製品として頒布することもできる。オープンソースソフトウェアで複数の共同開発者を持つことは、ソフトウェアの発展を手助けする。-- Wikipedia
実行環境
macOS
OS: macOS 13.5.2 22G91 arm64
Host: MacBookPro18,1
Kernel: 22.6.0
Docker version 24.0.6, build ed223bc
Docker Compose version v2.23.0-desktop.1
Linux
OS: Arch Linux x86_64
Host: MacBookAir7,2 1.0
Kernel: 6.5.9-arch2-1
Docker version 24.0.7, build afdd53b4e3
Docker Compose version 2.23.0
Elixir
- Elixir: 1.15.7
- Erlang: 26.1
- Phoenix: 1.7.10
問題1: Mac 側の GID が衝突してしまう
厳密にいうと @koyo-miyamura さんの記事で既に解決されています。@koyo-miyamura さんの記事ではマルチステージビルドで2種類のビルドターゲットを用意し、ホストマシンのOSに応じて使い分けるという技で解決されていました。しかしながら、ホストマシンを設定する人間が自分で判断してセットアップ手順を使い分けないといけないという問題は残ります。
ここでは、ビルドターゲットを分けずに共通のセットアップ手順で解決する方法に取り組んでみます。
解決案
Dockerfile
の中でホストマシンの OS により条件分岐するというやり方を検討してみようと思います。
@koyo-miyamura さんの記事で、問題の現象が macOS で group の設定を行うときだけに起きることがわかっているので、ホストマシンが macOS の時に group の設定をやらないようにすれば上手くいくはずです。
それを実現するには、いくつか調査が必要です。
-
Dockerfile
の中で条件分岐するスクリプトを書く方法 -
Dockerfile
の中でホストマシンの OS を検知する方法
同様の問題に取り組んでいる Github Issue を一つ見つけました。
Dockerfile の中で条件分岐するスクリプトを書く方法
調査したところ、簡単そうなのは RUN や ENTRYPOINT で普通にシェルスクリプトを書くというやり方です。
RUN を使って直接 Dockerfile
の中でスクリプトを書くことにします。
RUN echo piyopiyo
RUN が水面下で /bin/sh -c
を実行しているとのことです。Bash ではありません。どうしても Bash を使いたい場合は書き方に工夫が必要となります。
RUN /bin/bash -c 'echo piyopiyo'
試行錯誤の結果こんな感じで書けることがわかりました。このスタイルで行きます。
RUN if [ "$HOST_OSTYPE" = "Linux" ]; then echo "This is a Linux machine."; fi
RUN に渡すスクリプトはワンライナーでなければなりません。でもそれだと人間にはみにくいので、バックスラッシュ(\
)で分割して適当に見た目を整えます。
RUN if [ "$HOST_OSTYPE" = "Linux" ]; then \
echo "This is a Linux machine."; \
else \
echo "This is not a Linux machine."; \
fi
シェルスクリプトの変数はダブルクォートした方がいいそうなので、そうするように心がけています。
本件の場合はダブルクォートしなくてもいいのですが、今後うっかり罠にかからないよう自分のスクリプトでは文字列や変数を冗長的にダブルクォートする方針にします。そうすることにより、テキストエディターでもシンタックスハイライトが見やすくなるという利点もあります。
RUN if [ $HOST_OSTYPE = Linux ]; then echo This is a Linux machine.; fi
Dockerfile の中でホストマシンの OS を検知する方法
ホストマシンの OS を検知する方法は簡単です。uname
コマンドがあります。
uname
macOS だと Darwin
、Linux だと Linux
という文字列が買えると思います。
あとは @koyo-miyamura さんの記事で紹介されている方法を応用します。
Dockerfile
に引数を渡します。引数として受け取りたい変数名を ARG を用いて明示します。
ARG HOST_OSTYPE
@koyo-miyamura さんの記事では、引数をホストマシンの .env
から docker-compose.yml
へ、docker-compose.yml
から Dockerfile
へと渡していく技が紹介されていました。
問題2: alpine 以外のイメージで Dockerfile を書く
どのイメージを使うか決定
Phoenix 公式ドキュメントで紹介されている Docker イメージでやってみようと思います。
Debian GNU/Linux をベースにしたイメージが使用されています。
最新版のイメージを探す
最新版を Dockerhub で検索します。
Dockerfile で実行するコードの調整
依存関係のインストールや user/group の設定が OS により異なるので適宜調整が必要です。ネット検索で使えそうなものを探します。
できたもの
FROM hexpm/elixir:1.15.7-erlang-26.1.1-debian-bookworm-20230612-slim
ARG HOST_USER_NAME
ARG HOST_GROUP_NAME
ARG HOST_UID
ARG HOST_GID
ARG HOST_OSTYPE
# install build dependencies
RUN apt-get update -y && apt-get install -y \
build-essential \
git \
inotify-tools \
nodejs \
npm \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
# sync user
RUN if [ "$HOST_OSTYPE" = "Linux" ]; then \
addgroup --gid "$HOST_GID" "$HOST_GROUP_NAME"; \
adduser --uid "$HOST_UID" --gid "$HOST_GID" "$HOST_USER_NAME"; \
else \
adduser --uid "$HOST_UID" --gid "$HOST_GID" "$HOST_USER_NAME"; \
fi
USER $HOST_USER_NAME
# install hex + rebar + phx_new
RUN mix local.hex --force && \
mix local.rebar --force && \
mix archive.install --force hex phx_new
inotify-tools がないとこういうエラーがでます。このエラーを見たら思い出してください。
[error]
inotify-tools
is needed to runfile_system
for your system, check https://github.com/rvoicilas/inotify-tools/wiki for more information about how to install it. If it's already installed but not be found, appoint executable file withconfig.exs
orFILESYSTEM_FSINOTIFY_EXECUTABLE_FILE
env.
nodejs
と npm
は DaisyUI 等の Tailwind CSS のプラグイン を使いたい場合などに必要となります。初期設定の Phoenix アプリではそれらは不要です。
[debug] Downloading esbuild from https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz
Rebuilding...
Error: Cannot find module 'tailwindcss/plugin'
Require stack:
- /app/assets/tailwind.config.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function._resolveFilename (pkg/prelude/bootstrap.js:1955:46)
at Function.resolve (node:internal/modules/cjs/helpers:108:19)
at _resolve (/snapshot/tailwindcss/node_modules/jiti/dist/jiti.js:1:241025)
at jiti (/snapshot/tailwindcss/node_modules/jiti/dist/jiti.js:1:243309)
at /app/assets/tailwind.config.js:4:16
at jiti (/snapshot/tailwindcss/node_modules/jiti/dist/jiti.js:1:245784)
at /snapshot/tailwindcss/lib/lib/load-config.js:37:30
at loadConfig (/snapshot/tailwindcss/lib/lib/load-config.js:39:6)
at Object.loadConfig (/snapshot/tailwindcss/lib/cli/build/plugin.js:135:49) {
code: 'MODULE_NOT_FOUND',
requireStack: [ '/app/assets/tailwind.config.js' ]
}
** (Mix)mix tailwind default
exited with 1
さいごに
今回の作業を通して、Docker コンテナをさまざまな開発マシンで動かす技を習得するとともに、Docker、Docker Compose、Linux 等についての理解を深めることができました。
本記事は autoracex #253 の成果です。ありがとうございます。