Featured image of post Turborepo + PNPM + Remixで構成されたアプリケーションをFly.ioにデプロイする

Turborepo + PNPM + Remixで構成されたアプリケーションをFly.ioにデプロイする

Twitter ツイート Hatena Bookmark ブックマーク

Turborepoでmonorepo構成で、パッケージマネージャーはpnpm、アプリケーションのフレームワークにはRemixを採用したアプリケーションをFly.ioにデプロイするのに苦労しました。

Fly.ioのRun a Remix Appや、Monorepo and Multi-Environment Deploymentsなどの記事もあるし、なんか情報あるだろうと思っていたら全然なかった…。

サンプル

サンプルコードを用意しました。
polidog/turborepo-pnpm-remix-fiyio-deploy-example

そもそもflyioがpnpmに対応していない

flyioにはfly(flyctl)コマンドが用意されています。 たとえば apps/web というremixアプリを作成して、fly launchします。

1
2
3
$ cd apps/
$ pnpm dlx create-remix@latest web
$ fly launch

fly launchを実行すると以下のファイルが作成されます。

  • dockerignore
  • fly.toml
  • Dockerfile

ここで生成されたDockerfileはremixアプリの実行環境なわけですが、pnpmには対応できてません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=18.4.0
FROM node:${NODE_VERSION}-slim as base

LABEL fly_launch_runtime="Remix"

# Remix app lives here
WORKDIR /app

# Set production environment
ENV NODE_ENV=production


# Throw-away build stage to reduce size of final image
FROM base as build

# Install packages needed to build node modules
RUN apt-get update -qq && \
    apt-get install -y python-is-python3 pkg-config build-essential 

# Install node modules
COPY --link package.json .
RUN npm install --production=false

# Copy application code
COPY --link . .

# Build application
RUN npm run build

# Remove development dependencies
RUN npm prune --production


# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

# Start the server by default, this can be overwritten at runtime
CMD [ "npm", "run", "start" ]

おそらく flyctl/scanner/node.go のこのあたりのコードをみるとpnpmではなくyarn or npmなのでおそらく対応できてないかなと思います。 https://github.com/superfly/flyctl/blob/c37a567828b464eb097defdf01f30ef0bb732884/scanner/node.go#L83-L93

しかもこれだとmonorepoにも対応できてません。

Dockefileを自分で用意する

ということで、自分でDockerfileを修正していくことにしました。 ベースはfly launchで生成されたdockerfileを修正しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=18.4.0

FROM node:${NODE_VERSION}-slim as base
ARG SCOPE

LABEL fly_launch_runtime="Remix"

# ENV NODE_ENV=production

# Install packages needed to build node modules
RUN apt-get update -qq && \
   apt-get install -y python-is-python3 pkg-config build-essential 

ENV PNPM_HOME="/root/.local/share/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN npm i -g pnpm

RUN pnpm add turbo -g


WORKDIR /app
COPY . .

# setup
FROM base as setup
ARG SCOPE

RUN turbo prune --scope=web --docker

# Install & Build packages
FROM base as build

COPY .gitignore .gitignore
COPY --from=setup /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=setup /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
RUN pnpm install --prod=false

COPY --from=setup /app/out/full/ .
COPY turbo.json turbo.json
RUN pnpm run build --filter=web

# Runner 
FROM base as runner
ARG SCOPE
ENV NODE_ENV=production

COPY .gitignore .gitignore
COPY --from=build /app/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=build /app/pnpm-workspace.yaml ./pnpm-workspace.yaml

RUN pnpm install --prod --filter=web

COPY --from=build /app/apps/web ./apps/web

WORKDIR /app/apps/web
CMD [ "pnpm", "run", "start", "--filter", "web" ]

修正内容

今回はbase,setup,build,runnerという4つのステージを用意してます。

ステージ 内容
base 全体のイメージ共通での設定
setup パッケージインストール、ビルド前にすること
build パッケージインストール、ビルド
runner 実行環境

という形で分けています。

turbo pruneについて

setupのステージで turbo prune --scope=web --docker コマンドを実行してプルーニングしています。

プルーニングとは?

プルーニングとは、ビルド キャッシュの過成長を効率的に自動的に削除するプロセスです。プルーニングは、ロック ファイル内の依存関係のプルーニングを含め、monorepo のサブセットを作成します。

出典: What is Turborepo and Why Should You Care?

turbo pruneの場合、指定したscopeに最適化されたmonorepoが生成されます。
試しに手元で実行してみると outというディレクトリが作成されます。

1
$ pnpm dlx turbo prune --scope=web

apps/web が依存しているパッケージを中心にmonorepoがoutディレクトリの中に生成されています。

dockerオプション

turbo pruneにはdockerオプションがあります。 これはDocker環境でビルドするときにDockerのキャッシュを最適にするために必要なオプションになります。

キャッシュの効率化については、以下の記事を参考にしていただければと思います。

雑に言ってしまえば、変更が入りやすいものはなるべくDockerfileの後ろにもっていくほうがいいということでいいのかな?
パッケージインストールまでを先に行って、ソースコードのCOPYを行うことで効率的にDockerのキャッシュが使えるということかと思います。

Runnerステージについて

一度Buildしてしまえばnode_modulesはいらなくなるのかなと思っていましたが、そうでもなかったです。
flyioの場合 remix-serve build がproductionで実行されますが、結局nodeのランタイムで動くのでnode_modulesが必要になってしまいます。

ただしruntimeに必要なものだけになるのでdevDependenciesが必要なわけではないので --prod オプションを付けています。
NODE_ENV=prodctionなんでいらない気もしますが、わざと明示的につけています。

1
RUN pnpm install --prod --filter=web

サーバ側のランタイムで必要なnode_modulesだけをインストールしたかったんですが方法が見つからないので諦めました。

.dockerignoreについて

プロジェクトルートにdockerignoreを配置する必要があります。 node_modulesをignoreしたいのですが (apps|packages)/xxxx/node_modules を拒否したいの ** を使って指定します。
(.gitignoreとちょっと違うの辛い…)

1
2
3
**/node_modules
**/fly.toml
.git

node_moduelsをignoreしないとビルド途中にエラーになります。

1
ERROR: failed to solve: cannot copy to non-directory:

デプロイする

プロジェクトルートからfly deployコマンドを実行します。

1
$  fly deploy --config apps/web/fly.toml --dockerfile apps/web/Dockerfile  --remote-only

Dockerイメージのサイズについて

どうしてもDockerイメージが1GBを超えてしまう。。。どうにかならないものか。。。。

最後に

自分のフロントエンド力とかDocker力が低いのでまだまだ改良の余地があるかと思います。
ただ現状これでfly.ioで動かすことはできました。

変更したほうが良い箇所があったら、教えていただけると本当に助かります。

参考

comments powered by Disqus
Built with Hugo
テーマ StackJimmy によって設計されています。