何の記事?
herokuが有料化されてから、アプリのホスティングをどこにするかと悩まれている方も多いはずの今日この頃。
かくいう私も趣味サイトのデプロイをどこにしようか迷っていたところ、fly.ioが要件をいい感じに満たしてくれそうだったので、色々と遊びながら、デプロイまで漕ぎ着けてみました。
今回は、fly.ioにデプロイするまでの過程や注意点を放流🐟していこうと思います。
アプリ構成
- FW: next.js
- ホスティング: fly.io
- データベース: postgres
- ドメイン: muumuu-domain
(重要な部分だけ)
前提
fly.ioの会員登録は終わっている
flyctlはインストール済み(ちな、brewではbrew install flyctl
で一発
next.jsはデプロイできる状態まで準備済み
prisma使ってる
作業ディレクトリは、next.jsのroot
❯ pwd # -> /Users/🐱/Desktop/dev/myapp
実装
fly.ioでアプリを動かすには、flyctlコマンドを通じてデプロイまで操作していきます。
デプロイまでの手順としては、アプリの基礎情報の作成、環境変数の設定、データベースの設定、そしてドメインの設定といったステップで進めます。
アプリの基礎情報
flyctl launch
コマンドを実行すると、fly.ioにアプリをアップロードするまでに必要な情報をプロンプトに従って入力することで、デプロイまでを一気に行うことができます。
❯ flyctl launch
Creating app in /Users/🐱/Desktop/dev/myapp
Scanning source code
Detected a Dockerfile app
? Choose an app name (leave blank to generate one): myapp # ①
? Select Organization: my-super-secret@email.com (personal) # ②
? Choose a region for deployment: Tokyo, Japan (nrt) # ③
Created app myapp-37 in organization personal
Wrote config file fly.toml
? Would you like to set up a Postgresql database now? No # ④
? Would you like to set up an Upstash Redis database now? No # ⑤
? Would you like to deploy now? No # ⑥
Your app is ready! Deploy with `flyctl deploy`
さて、今回はローカルのnext.jsアプリディレクトリからflyctl launch
を実行して、上記のように設定を行いました。
設定を行うと以下の3つのファイルが作成されます
.dockerignore
Dockerfile
fly.toml
①アプリ名の設定
fly.ioに展開するアプリ名です。これはfly.ioで、ホスト名として利用される為、他の人と被らないようにユニークにする必要があるので要注意。
アプリ名がhogeであればhttps://hoge.fly.devというurlが発行されます。
②組織名
この組織名に関しては、fly.ioのダッシュボードから予めorganizationを作成してあれば、personal以外にも選択が可能になります。今回は、特に指定がないので、脳死で先に進みます。
後々、origanizationを変更したいという時は、flyctl apps move -a myapp -o cat-lovers-organization
とflyctlから変更が可能です。
③リージョン
リージョンは、今回はtokyoを選択。
④postgresql
launchコマンド実行と同時に、postgresのセットアップを一緒に行うかを尋ねられます。
デフォルトのオプションでは以下のように、選択項目が表示されますが、今回は後々設定して作りたいのでNを選択。
Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
Production - Highly available, 2x shared CPUs, 4GB RAM, 40GB disk
Production - Highly available, 4x shared CPUs, 8GB RAM, 80GB disk
Specify custom configuration
⑤Redis
今回は不要なのでN
⑥直ぐにデプロイするか
後述しますが、環境変数の設定をDockerfileに行わないといけないのでN
環境変数の設定
fly.ioでnext.js運用時は、利用する環境変数の種類に応じて設定方法が異なってきます。
secretな環境変数
クライアントに表示したくない秘匿性の高い環境変数は、flycltから値をセットします。
❯ flyctl secrets set -a myapp MY_SUPER_SECRET_KEY="this is my super secret key" MY_ANOTHER_SUPER_SECRET_KEY=1234
publicな環境変数
next.jsではクライアントでも参照可能な環境変数にはNEXT_PUBLIC_とprefixをつけてあげるのが必須になっています。(仮にパブリック環境変数と呼ぶことにします。
このパブリック環境変数は前項目(secretな環境変数)で行ったようなflyctlから環境変数に値をセットしても動きません。パブリック環境変数はDockerイメージをビルド時に値として渡しあげる必要があるので、Dockerfileとfly.tomlファイルを編集することで対応していきます。
今回は、NEXT_PUBLIC_FRUITというパブリック環境変数があると仮定し、以下のように各ファイルへ編集を行います。
# rootディレクトリに作成されたfly.toml
# ...
[build]
[build.args]
NEXT_PUBLIC_EXAMPLE = "Value goes here"
NEXT_PUBLIC_FRUIT = "APPLE"
#...
ARG NEXT_PUBLIC_FRUIT
データベースの設定
今回はデータベースを操作するのにPrismaを利用していると仮定します。
Prismaのデータベースの接続先の値では、schema.prismaファイルで設定してありDATABASE_URLという環境変数を参照している。今回はfly.io上で稼働しているpostgresサーバーを利用するので、postgresサーバーのホスト名を取得する必要があります。
その為、postgresサーバーの作成->urlの取得といった手順を踏んでいきます。
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
DBアプリの作成
今回は先ほど、postgresの作成をスキップしたので、flyctlからコマンドを実行してpostgres用マシーンの作成をします。
❯ fly postgres create
? Choose an app name (leave blank to generate one): my-sample-db # ①
? Select Organization: my-super-secret@email.com (personal) # ②
? Select region: Tokyo, Japan (nrt) # ③
? Select configuration: [Use arrows to move, type to filter]
Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk # 1
Production - Highly available, 2x shared CPUs, 4GB RAM, 40GB disk # 2
Production - Highly available, 4x shared CPUs, 8GB RAM, 80GB disk # 3
> Specify custom configuration # ④
? Initial cluster size 2
? Select VM size: shared-cpu-1x - 256
? Volume size 1
①、②、③に関してはnext.jsのでflyctl launch
を実行した時と同じなので説明は省略
④設定
postgresマシンスペックの設定項目では、デフォルトで4つの選択項目が存在します。
developmentとproductionの一番大きな違いは、スペック以外にも読み込み、読み書きと専用のクラスターが提供されるということです。(development(1)はクラスターが1つ。production(2)では2つ。(3)は未確認)
さて、どれだけ小規模なアプリでも本番運用を想定すれば、productionを選択したいところですが、ここで選択できるほどのスペックを必要としない場合、無駄にコストがかってしまうのでカスタムを選択することになります。
今回は以下のように設定を進めました。
...
? Select configuration: Specify custom configuration
? Initial cluster size 2
? Select VM size: shared-cpu-1x - 256
? Volume size 1
Creating postgres cluster in organization personal
Creating app...
Setting secrets on app my-sample-db...
Provisioning 1 of 2 machines with image flyio/postgres:14.4
Waiting for machine to start...
Machine 3d8d1adwced189 is created
Provisioning 2 of 2 machines with image flyio/postgres:14.4
Waiting for machine to start...
Machine 1781774a932489 is created
==> Monitoring health checks
Waiting for 3d8d1adwced189 to become healthy (started, 3/3)
Waiting for 1781774a932489 to become healthy (started, 3/3)
Postgres cluster my-sample-db created
Username: postgres
Password: mysupersecretpassword
Hostname: my-sample-db.internal
Proxy port: 5432
Postgres port: 5433
Connection string: postgres://postgres:mysuperpassword@my-sample-db.internal:5432
また、クラスターを複数作成で選択すると「読み書き、読み込みのみ(レプリカ)」といい感じに動作モードを設定してくれるようです。
最後に表示されるユーザー名とパスワードは、大事に保管しておきましょう。
補足しておくと、psql接続以外にもflyctlコマンドでpostgresには接続可能で、自動的にユーザーがpostgresで選択された状態で接続可能です。
動作確認
今回はクラスターを2個としたので、動作確認で作成したデータベースに接続し、読み書きを両方で行ってみます。
今回作成したサーバーの情報
--- id --- | --- モード --- | --- ポート --- |
---|---|---|
3d8d1adwced189 | 読み書き | 5432 |
1781774a932489 | 読みのみ | 5433 |
flyctlではflyctl postgres connect -a <postgres-app-name>
のコマンドでも接続が可能ですが、flyctlでproxyを通すことで、psql接続も可能になります。
今回は、せっかくなのでproxyを通して接続してみたいと思います。
test_dbというのを予め作成、fruitテーブルを準備してあります。
# terminal 1
# ローカルのdbポートと喧嘩するので適当に5455にバインドしてます
❯ flyctl proxy 5455:5432 -a my-sample-db
# terminal 2
❯ psql postgres://postgres:mysupersecretpassword@localhost:5455/test_db
...
psql (14.6 (Homebrew), server 14.4 (Debian 14.4-1.pgdg110+1))
Type "help" for help.
test_db=# insert into fruit values ('lemon');
INSERT 0 1
test_db=# select * from fruit;
name
-------
lemon
(1 row)
問題なく書き込みができていることが確認できました。
では、次にもう一つのサーバーの動作を確認してみましょう。ポート5433に接続し書き込み失敗すれば意図した通りの動作です。
# terminal 1
❯ flyctl proxy 5455:5433 -a my-sample-db
Proxying local port 5455 to remote [my-sample-db.internal]:5433
# terminal 2
❯ psql postgres://postgres:mysupersecretpassword@localhost:5455/test_db
psql (14.6 (Homebrew), server 14.4 (Debian 14.4-1.pgdg110+1))
Type "help" for help.
...
test_db=# insert into fruit values('apple');
ERROR: cannot execute INSERT in a read-only transaction
test_db=# select * from fruit;
name
-------
lemon
(1 row)
前途した通り動作していることが確認できました。
prismaから繋がるように
Prismaからpostgresサーバーに繋がるようにするには、前途した通りDATABASE_URLにpostgresのアドレスを指定しないと動作しません。このアドレスですが、基本的には先ほどのDBを作成した際に出力されるHostname: my-sample-db.internal
で出力されたホストを指定することになります。
❯ flyctl secrets set -a myapp DATABASE_URL="postgres://postgres:mysupersecretpassword@my-sampledb.internal:5432/test_db"
さて、ここまで来ればflyctl deploy
コマンドを実行してアプリをデプロイしておきましょう。
ドメインの適用
今回は大昔から運用している個人アプリで、ドメインをmuumuuドメインで取得していたので、muumuuドメインを例に説明しています。
デプロイしたnext.jsアプリのip取得
fly.ioのダッシュボードから確認することも可能ですが、flyctlでサクッとDNS設定に必要なipを表示します。
❯ flyctl ips list -a myapp
VERSION IP TYPE REGION CREATED AT
v4 123.456.789.xx public global 2022-11-26T12:29:27Z
v6 xxxx:xxxx:z::xxxx public global 2022-11-26T12:29:32Z
ここで取得したものを、DNSレコードに適応しておきます。ちなみに、muumuuドメインはこんな感じのフォームが用意されてます。
サブドメイン | 種別 | 内容 | 優先度 |
---|---|---|---|
空 | A | 123.456.789.xx | |
空 | AAAA | xxxx:xxxx:z::xxxx |
設定を反映したら、あとはflyctlから証明書を発行するだけです。ちなみに、2つ目のコマンドは証明書が発行されたかどうか確認するコマンドなのでやらなくても良いです。
❯ flyctl certs create -a myapp example.com
❯ flyctl certs show example.com
あとは、反映されればfin!
おまけ
サーバー側で何かしらする時に権限エラーが出る
例えば一時ファイルを作った時とか。
サーバーのコード実行時のユーザーはnodeなのでDockerイメージ生成時のファイルコピー時に、nodeに権限を付与しておきましょう。
# ファイルコピー時にnodeに権限付与
COPY --from=builder --chown=node:node /app ./
デプロイできない
色々遊んでいると"ENOSPC: no space left on device, write"
的なエラーが出たりして、デプロイできない問題にちょくちょくぶち当たる。これは、fly-builder-xxx
という名前のビルド専用アプリが、過去に作ったdockerイメージの容量で圧迫されて限界がきて発生しているっぽいです。
ということで、このビルド専用インスタンスを作り直す(もしくは、アプリに接続して不要なイメージを削除🤔?。 シンプルに削除するには、ダッシュボードからアプリ一覧を開くとfree builder
タグがついてるものがあるので、その設定からdeleteを実行する。
また、このビルド専用アプリはflyctl deploy
コマンドを実行すると勝手に作成されるので、特別、自身で作成しないといけない。なんてことはないのでご安心ください。