Railway をやめて VPS にした理由 —「Compose をそのまま持っていける」は私の思い込みだった
作ったアプリの公開先に Railway を選び、デプロイは通るのにアプリが起動せず、結局 VPS に移しました。原因は特定できていません。ただ公式ドキュメントを読むと、Railway は docker-compose.yml をそのまま実行しないと最初に書いてあります。PaaS と VPS で迷っている人が、選ぶ前に読むべき場所の話です。
本記事にはアフィリエイトリンクを含みます
作ったアプリを公開する段になると、置き場所を選ぶことになります。
PaaS(Railway、Render など) … コードを渡すと、あとはやってくれる
VPS(ConoHa、XServer など) … サーバーを1台借りて、自分で全部やる
私は前者を選びました。理由ははっきりしていて、手元の Docker Compose の構成をそのまま持っていけると思ったからです。
結果を先に書くと、動かせませんでした。デプロイは成功するのに、アプリが起動しない。原因を突き止められないまま VPS に移し、そちらでは今も動いています。
しばらくは「運が悪かった」くらいに思っていました。ところが最近、Railway の公式ドキュメントをちゃんと読んで、自分の前提がそもそも間違っていたと分かりました。しかもそれは、ページのいちばん上に書いてありました。
この記事は、その記録です。Railway が悪いという話ではありません。読まなかった私の話です。
なぜ Railway を選んだのか
当時の判断は、自分のリポジトリに残っていました。要件定義のファイルです。
docs/requirements_02_system_architecture.md:43
ホスティング先:Railway(Docker Compose構成をそのまま持っていけるため採用。次点候補はRender)
docs/CLAUDE_addendum_en.md:16
Production hosting: Railway (preferred, can lift the existing Docker Compose
setup directly). Render is the fallback option.
日本語でも英語でも、同じことを書いています。採用理由は「Compose 構成をそのまま持っていける」の一点でした。
想定していた流れもこう書いてありました。
ローカル検証 → GitHubへpush → GitHub ActionsでCI → Railwayへ自動デプロイ
きれいな絵です。そして現在この通りには一切なっていません。今は VPS に手で入れています。
実際に起きたこと
デプロイは通りました。ビルドも成功します。でもアプリが起動しませんでした。
正直に書くと、ここから先を私は詳しく説明できません。
- 原因を特定していません
- 当時のログも残っていません
しばらく粘って、諦めて VPS を借りました。この記事で「原因はこれでした」と書けたら読みやすいのですが、書けるだけの材料が手元にありません。後半でこの点にもう一度戻ります。
公式ドキュメントを読んだら、最初の行に書いてあった
今回この記事を書くにあたって、Railway の公式ドキュメントを開きました。「Deploy a Docker Compose App to Production」というページです。
Railway does not run
docker-compose.ymlfiles directly. Instead, each service defined in your Compose file maps to a separate Railway service within a project. The sections below walk through that translation so you can take an existing Compose setup and deploy it to production without managing servers.
訳すとこうなります。
「Railway は docker-compose.yml を直接実行しません。かわりに、Compose ファイルで定義された各サービスが、プロジェクト内の個別の Railway サービスに対応づけられます。以下のセクションでは、その翻訳の手順を説明します」
一文目で終わっています。そのまま実行しないと書いてあります。
そして注目したいのが三文目の translation という単語です。公式自身が、この作業を翻訳と呼んでいます。私が採用理由に書いた「そのまま持っていける」の、ちょうど反対語が公式ドキュメントの中にありました。
公式が示している対応表
同じページに、Compose の書き方が Railway では何になるかの対応表があります。以下は原文をそのまま引用したものです(訳は付けず、この下で説明します)。
| Compose concept | Railway equivalent |
|---|---|
services: entry | Railway service |
build: | Deploy from GitHub repo with a Dockerfile |
image: | Deploy from a Docker image |
ports: | Public networking (external) or automatic private networking (internal) |
volumes: | Railway Volumes |
networks: | Automatic private networking (no config needed) |
environment: / env_file: | Railway Variables |
depends_on: | No direct equivalent. Applications should handle connection retries at startup. |
左の列は、Compose ファイルに書く項目です。右の列が Railway 側での対応物です。
ほとんどの行に「対応するものはある」と書いてあります。移行できないわけではありません。ただし、それぞれ手で読み替える必要があるということでもあります。「ファイルを置けば動く」ではありません。
そして最終行だけ、書いてあることの質が違います。
とくに効いてくる3つの記述
1. depends_on に相当する機能がない
The
depends_ondirective has no Railway equivalent. If the web service starts before the database is ready, it should retry the connection. Most database client libraries and ORMs support this through connection retry configuration.
「depends_on ディレクティブに Railway での相当物はありません。Web サービスがデータベースの準備完了より先に起動した場合、接続を再試行すべきです。ほとんどのデータベースクライアントライブラリと ORM は、接続リトライの設定でこれに対応しています」
depends_on は Compose で「このプログラムはデータベースが起動してから立ち上げてね」と順番を指定する書き方です。Railway にはこれがないので、アプリ側で「つながらなかったらもう一度試す」処理を書いておく前提になります。
ここで注目してほしいのは、公式が「無い」で終わっていないことです。対処法まで書いています。この記事の結論に関わるので、覚えておいてください。
2. ポートマッピングの層が存在しない
Note: Use the port your application actually listens on when connecting over the private network. There is no port mapping layer.
「注意:プライベートネットワーク経由で接続する際は、アプリケーションが実際に待ち受けているポートを使ってください。ポートマッピングの層はありません」
Compose では ports: "80:80" のように「外の80番を中の80番につなぐ」という書き方をします。この変換の層が Railway には無いので、アプリが実際に開いている番号をそのまま使うことになります。
3. サービス同士の呼び名が違う
Compose では、サービス名をそのままホスト名として使えます。db という名前のサービスなら db で届きます。Railway では <service-name>.railway.internal という形になります。
さらに、データベースへの接続情報は reference variables という仕組みを使うことが勧められています。公式はこう書いています。
Example: instead of hardcoding
DATABASE_URL=postgres://user:pass@db:5432/myapp, create a reference variable that pulls the value from the Postgres service'sDATABASE_URLvariable.
「例:DATABASE_URL=postgres://user:pass@db:5432/myapp と直接書き込むかわりに、Postgres サービスの DATABASE_URL 変数から値を引く参照変数を作ってください」
instead of hardcoding——直接書くのはやめて、と名指しされています。そして名指しされているその書き方は、Compose で普通に使う書き方です。
原因は、今も特定していません
ここが、この記事でいちばん正直に書きたいところです。
上の3つは、私が遭遇した「デプロイは通るのにアプリが起動しない」という症状と、きれいに噛み合います。
| 公式の記述 | 症状との噛み合い |
|---|---|
depends_on に相当機能がない | 各サービスが独立に起動するので、DBの準備前にアプリが立ち上がって落ちうる |
| ポートマッピング層が無い | ports: "80:80" 前提の構成が、そのままでは意図どおりに繋がらない |
| ホスト名の解決が別物 | postgres://…@db:5432/… のような、Compose のサービス名を書いた接続文字列は解決できない |
読んだとき、正直「これだったのか」と思いました。
でも、それは書けません。
当時のログが残っていない以上、この3つのどれかが原因だったと証明する材料が私の手元にありません。噛み合う説明が立つことと、原因が分かったことは違います。
「原因は depends_on でした」と書けば、記事としてはずっと読みやすくなります。でもそれは、確かめていないことを確かめたように書くことになります。
つまり、障害ではなく設計時の思い込みでした
整理するとこうなります。
- 私は「Compose をそのまま持っていける」という理由で Railway を選んだ
- その前提は、公式ドキュメントの一文目で否定されている
- 動かなかった具体的な原因は分からない。ただし公式の記述と症状は噛み合う
- 読んでいれば、移行はできた
4 が大事だと思っています。公式ドキュメントは、対応表を出し、手順を追い、depends_on については対処法まで書いていました。不親切ではありません。読めば移行できました。
私がやったのは、「Compose が使えるらしい」という理解のまま設計を確定させ、要件定義に書き、そのまま進めたことです。確認していないことを、確認したつもりで前提にしていました。
VPS に移して、今も動いている
移した先は ConoHa VPS の2GBプランです。そちらは46日連続で動いていて、実際にメモリを測った記録が別の記事にあります。
→ VPSのメモリは何GB必要か — 個人開発のアプリを46日動かして実際に測ってみた
移してから気づいたのは、私の運用の仕方が、そもそもサーバーに入る前提でできていることでした。
# 記事の本文をデータベースの中で直接書き換える
sudo docker compose ... exec -T backend python manage.py shell
# データベースのバックアップを取る
sudo docker compose ... exec -T postgres pg_dump ...
# 設定ファイルが最終的にどう解釈されるかを確認する
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
どれも「SSH で入って、自分の Docker を直接触る」形です。これは PaaS が得意な形ではありません。
だから今の結論は「Railway より VPS が優れている」ではなく、「私の手の動かし方には VPS が合っていた」です。逆に、サーバーに入らない・入りたくない運用なら、PaaS のほうがずっと楽です。
選ぶ前に、どこを読めばいいか
同じ失敗をしないために、私が今やるならこうします。
1. 使う予定の構成の名前で、公式ドキュメントを検索する
「Docker Compose」でも「Django」でも構いません。自分が持っていこうとしているものの名前で検索してください。
Railway の場合、その名前で検索すれば今回のページに着きます。そして一文目で答えが出ます。所要時間は5分もかかりません。
2. 「対応表」を探す
移行の手引きに対応表があるなら、それはそのままでは動かないという意味です。そのまま動くなら表は要りません。
表があること自体が答えです。
3. 「対応するものがない」と書かれた行だけメモする
対応表の中で、No direct equivalent(相当物なし)と書かれた行が、あなたが自分で手当てしなければならない箇所です。今回でいえば depends_on の行がそれでした。
4. どちらも試せるなら、小さく試す
VPS は1か月分だけ借りて壊す、ということができます。月1,000円ほどで、実際に自分の構成が動くかを確かめられます。設計を確定させる前に確かめるのが、いちばん安く済みます。
まとめ
「そのまま持っていける」と書いてある構成でも、何が「そのまま」で何が「翻訳」なのかは、自分で確かめないと分かりません。
私の場合、確かめるのに必要だったのは公式ドキュメントを5分読むことだけでした。それをせずに設計を決めて、動かず、サーバーを借り直しました。
そしてこの記事にも、分からないままの部分が残っています。起動しなかった原因は、今も特定できていません。噛み合う説明はありますが、それは説明であって証拠ではありません。
次にやること
置き場所を決めかねているなら、今日この順番でやってみてください。
- 使う予定のサービス名 + 自分の構成名(例:
Railway docker compose)で公式ドキュメントを検索する - 移行の対応表があるか見る。あれば「翻訳が要る」ということ
No direct equivalentと書かれた行をメモする。そこがあなたの作業になる- それでも決められなければ、VPSを1か月だけ借りて実際に動かしてみる
サーバーを選ぶ話は、VPSのメモリは何GB必要か に実測値を載せています。プランで迷っているなら、そちらもどうぞ。
出典:Railway 公式ドキュメント「Deploy a Docker Compose App to Production」
https://docs.railway.com/guides/docker-compose(2026年8月13日 閲覧)
引用は原文のまま掲載し、訳を添えています。引用部分に、私が強調を加えた箇所はありません。