Docker で Remix チュートリアル環境を構築する
はじめに
フロントエンドは全然ド素人[1]だけどちょっとずつやっていこうと思う今日この頃。
試すにしても環境は汚したくない[2]ので、すべて Docker でやることにしているのだが、それはそれで面倒くさいので記事にしておく。
フロントエンドは React で作ろうと思ってフレームワークには Remix を選定[3]した。選定理由の参考にしたのは以下の記事である。
- https://zenn.dev/morinokami/articles/why-nextjs-summary
- https://techblog.a-tm.co.jp/entry/2023/01/05/165133
- https://www.epicweb.dev/why-i-wont-use-nextjs
- https://leerob.io/blog/using-nextjs
- https://remix.run/blog/remix-vs-next
簡単に言えば、Next.js は特有の API を使用していて可搬性がない(他のフレームワークへの乗り換えがしにくい)らしく、Remix のほうが Web 標準やピュアな React に則っているらしいからである。勉強を進めてあとから Next.js を使いたくなったとき Remix → Next.js の乗り換えはおそらく知識を広げる方向で学べばよいが、その逆は学び直しになるのだろうと私の勘が告げているので Remix にした。
Quick Start
まずは Remix の公式クイックスタートガイドの部分を Docker でできるようにする。
後々のことを考えて悩んだ結果、最初に取るディレクトリ構成は以下のようにした。
remix_devcon/
└ Makefile
つまりプロジェクトを作成するような Makefile
を書く。
Makefile の中身解説
Makefile の変数の定義方法は =
, :=
, ?=
の3種類がある。
=
は挙動がややこしいので以下の Makefile
では :=
と ?=
しか使っていない。
:=
は即時展開が行われ、右辺の値で左辺の変数を定義する。ここでは $(shell)
を使ってシェルコマンドの実行結果を変数に代入している。id -un
などはそのコマンドを実行しているユーザーの情報を取得するコマンドで、Docker コンテナの中でホストにおけるユーザーとまったく同じ Linux ユーザーを作成するために取得している。
?=
は条件付き代入で、左辺の変数が未定義の場合に右辺の値でその変数を定義する。NODE_IMAGE
, PROJECT_NAME
は条件付き代入で定義することで外部から与えることが可能になっている。たとえば
$ PROJECT_NAME=myapp make create_env
のように実行すれば、変数 PROJECT_NAME
の中身は myapp
になり、単に
$ make create_env
と実行すれば変数 PROJECT_NAME
の中身はデフォルト値の project
になる。
その後、docker
コマンドで -it
は簡単に言えばコンテナのシェルとホストのシェルを繋ぐ指定であり、これを指定しないとシェルは一瞬で終了してしまう。--rm
はコマンド実行終了後にコンテナを破棄するオプションである。
-p 3000:3000
はホスト側 3000 番ポートをコンテナ側 3000 番ポートにバインドしており、サーバーを起動してアクセスするにはこの指定が必要である。念のため、:
の左がホスト側、右がコンテナ側である。
-v ${PWD}/${PROJECT_NAME}:/project
は指定したプロジェクト名のディレクトリをコンテナ内の /project
にマウントする指定である。${PWD}
はしばしば sudo のときにも引き継がれるように別途設定する必要がある。
-v /project/node_modules
はコンテナ内にある /project/node_modules
ディレクトリをコンテナ内のディレクトリとして再マウントすることで、ホスト側ディレクトリのマウント対象から外す指定である。これをやっておくと node_modules
にインストールされる各種モジュールがホスト側のディレクトリを汚染することがなくなる。
-w /project
はコマンド実行時のカレントディレクトリ指定。-u root
はコマンド実行ユーザーの指定。
groupadd -g ${DEVEL_GID} ${DEVEL_GROUP} \
&& useradd -g ${DEVEL_GID} -u ${DEVEL_UID} -m -s /bin/bash ${DEVEL_USER} \
&& chown -R ${DEVEL_USER}:${DEVEL_GROUP} /project/node_modules \
&& su ${DEVEL_USER}
のあたりはホスト側の Linux ユーザーと同じユーザーを作成し、root
の所有でマウントされている /project/node_modules
の所有をコマンド実行ユーザーに与え、最後に新たに作成した Linux ユーザーとしてシェルにログインする。
また、ベースイメージには最低限のエディタもインストールされていないので apt-get
でインストールしている。
※ ${PWD}
は sudo 時も引き継がれるようにするか、PWD=`pwd` make create_env
のように明示的に与えてください。
NODE_IMAGE ?= node:21-bookworm
PROJECT_NAME ?= project
DEVEL_USER := $(shell id -un)
DEVEL_UID := $(shell id -u)
DEVEL_GROUP := $(shell id -gn)
DEVEL_GID := $(shell id -g)
create_env:
mkdir -p ${PROJECT_NAME}
sudo docker container run -it --rm \
-p 3000:3000 \
-v ${PWD}/${PROJECT_NAME}:/project \
-v /project/node_modules \
-w /project \
-u root \
${NODE_IMAGE} /bin/bash -c "\
groupadd -g ${DEVEL_GID} ${DEVEL_GROUP} \
&& useradd -g ${DEVEL_GID} -u ${DEVEL_UID} -m -s /bin/bash ${DEVEL_USER} \
&& chown -R ${DEVEL_USER}:${DEVEL_GROUP} /project/node_modules \
&& apt-get update \
&& apt-get install -y nano vim \
&& su ${DEVEL_USER} \
"
以下のコマンドを実行してみてほしい。
$ PROJECT_NAME=my-remix-app make create_env
すると Quick Start ガイドで言うところの
$ mkdir my-remix-app
$ cd my-remix-app
までが実行された状態でコンテナが立ち上がる。続きのコマンドを打ってみるとよい。注意点として、途中でコンテナ環境から exit
してしまうと node_modules
内のデータが消えるような設定になっているので、チュートリアルは一気にやること。
$ npm init -y
# install runtime dependencies
$ npm i @remix-run/node @remix-run/react @remix-run/serve isbot react react-dom
# install dev dependencies
$ npm i -D @remix-run/dev
$ mkdir app
$ touch app/root.jsx
$ vim app/root.jsx
import {
Links,
Meta,
Outlet,
Scripts,
} from "@remix-run/react";
export default function App() {
return (
<html>
<head>
<link
rel="icon"
href="data:image/x-icon;base64,AA"
/>
<Meta />
<Links />
</head>
<body>
<h1>Hello world!</h1>
<Outlet />
<Scripts />
</body>
</html>
);
}
また、package.json
の2行目に "type": "module",
を追記する。
$ vim package.json
{
"type": "module"
// ...
}
ビルドして serve する。
$ npx remix build
info building... (NODE_ENV=production)
info built (640ms)
$ npx remix-serve build/index.js
[remix-serve] http://localhost:3000 (http://172.17.0.2:3000)
この時点で同じコンピュータからであれば localhost:3000
または外部のコンピュータからであれば [IPアドレス]:3000
をブラウザのアドレス欄に入力すると以下のように Hello world! が表示される。
ここまでの作業をしたあとでコンテナ環境を exit
して remix_devcon
を tree
コマンドで見てみると以下のようになっている。
remix_devcon/
├── Makefile
└── my-remix-app
├── app
│ └── root.jsx
├── build
│ ├── index.js
│ ├── metafile.js.json
│ ├── metafile.server.json
│ └── version.txt
├── node_modules
├── package.json
├── package-lock.json
└── public
└── build
└── ...
クイックスタートにはまだ他の作業もあるのでお好みで続けるとよい。
Tutorial
Remix 公式にはクイックスタートのあとにチュートリアルがある。
クイックスタートのほうで用意したディレクトリ構成や Makefile
はチュートリアルでもそのまま使えるように工夫してある。
まだよく知らないが、おそらく Remix のプロジェクトは最初 npx create-remix@latest --template ...
から始まるので、これを使えるようにしておけば今後の開発でも使えるだろうという推測のもと、そのコマンドが機能するように Makefile
を作ってある。
$ PROJECT_NAME=remix_tutorial make create_env
あとはチュートリアル通りに進めるだけ。
$ npx create-remix@latest --template ryanflorence/remix-tutorial-template
既にプロジェクトのディレクトリの中にいるので、プロジェクト作成場所はカレントディレクトリ .
を指定するとよい。git
はインストールしていないので、git
による初期化は No
を選択しないとエラーになる[4]。Dependencies のインストールはどうせあとでやらざるを得ないので Yes でよい。
インストールが終わったら以下のコマンドでアプリを起動できる。
$ npm run dev
> dev
> remix dev
💿 remix dev
info building...
info built (1.1s)
[remix-serve] http://localhost:3000 (http://172.17.0.2:3000)
ここまでの作業を行なってコンテナ環境から exit
し、ディレクトリ構造を見てみると以下のようになっている。
remix_devcon/
├── Makefile
└── remix_tutorial
├── app
│ ├── app.css
│ ├── data.ts
│ └── root.tsx
├── build
│ ├── index.js
│ ├── index.js.map
│ ├── metafile.js.json
│ ├── metafile.server.json
│ └── version.txt
├── node_modules
├── package.json
├── package-lock.json
├── public
│ ├── build
│ │ └── ...
│ └── favicon.ico
├── README.md
├── remix.config.js
├── remix.env.d.ts
└── tsconfig.json
あとのことをダラダラとこの記事に書いても仕方がないので、あとは公式のチュートリアルを続けてください。
オマケ: 作ったアプリをデプロイするとき
公式の Deployment マニュアルには、自前のサーバーにデプロイするならそっちのドキュメントを読めと書いてある。
いやそうは言うたかて、どのファイルをデプロイしたらいいかを聞きたいんじゃが。あとはこれしか書いていない。
After initializing an app, make sure to read the README.md
チュートリアルで生成された README.md
を読むと以下のようなことが書いてある。
### DIY
If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
Make sure to deploy the output of `remix build`
- `build/`
- `public/build/`
オッケー。状況によっては変わる可能性もあるかもしれないが、とりあえず build/
と public/build/
さえあればよいらしい。
あとは create したときの環境を再現するために package.json
と package-lock.json
があればよい。
remix_devcon/
├ docker-compose.yml
├ Dockerfile
├ Makefile
└ project/
├ package.json
├ package-lock.json
├ build/
│ └ ...
├ public/
│ └ build/
│ └ ...
└ ...
ARG NODE_IMAGE
FROM ${NODE_IMAGE}
COPY project/package.json project/package-lock.json /project/
COPY project/build/ /project/build/
COPY project/public/build/ /project/public/build/
WORKDIR /project
RUN npm ci
version: '3.9'
services:
remix:
container_name: remix-server
build:
context: .
args:
NODE_IMAGE: "node:21-bookworm"
ports:
- "3000:3000"
command: ["npx", "remix-serve", "build/index.js"]
NODE_IMAGE
は docker-compose.yml
で与えておけば、以下のコマンドがそのまま使えるので楽である。デプロイ用のときだけイメージサイズの小さい node:21-alpine
などに変えることもできる。
$ sudo docker-compose build
$ sudo docker-compose up
一方で開発用とデプロイ用でイメージを揃えたい場合は Makefile
と2ヶ所に書いてあるのは管理する上で気づかないうちに齟齬が発生する可能性があるので、.env
などを活用して書いてある場所を1ヶ所に絞るか、あるいは Makefile
で
.PHONY: build_image
build_image:
sudo docker-compose build --build-arg NODE_IMAGE=${NODE_IMAGE}
のように --build-arg
で明示的に与えてもよい(この方法は docker-compose
を使用する際に make
を経由しなければならないという制約を課してしまうのであまりよくない)。
まぁその辺りはお好みで。
おしまい
Enjoy your Hacking Life!
-
本業はデータサイエンスだがなまじっかプログラミング歴が長いせいで分析インフラの構築などをやらされることが多い。そしていよいよフロントエンドからも逃げられなさそう。 ↩︎
-
私の遊び場は ConoHa VPS で基本的な環境構築は自動化されているとはいえ、汚すたびに立て直したりバックアップから復元するのは面倒くさい。 ↩︎
-
対抗は Next.js だった。 ↩︎
-
Makefile
の中のapt-get
でgit
をインストールしてもよいが、config をホスト側と共有するにはひと手間かかるし、この記事の趣旨から外れてしまうのでこの記事ではやらない。 ↩︎
Discussion