2026年8月29日
2026年8月29日
VPSでRuby on Railsをデプロイする方法(Puma+Nginx+systemdの手動構成)
VPS に Ruby on Rails のアプリを公開するには、「Ruby をバージョン管理ツールで入れ、アプリを Puma で動かし、systemd で常駐させ、Nginx を前段に置いて HTTPS 化する」構成が基本です。この記事では Ubuntu の VPS を例に、この構成を手作業で組む手順を説明します。仕組みを一度手で理解しておくと、Kamal や Capistrano などのデプロイツールを使うときも、問題が起きた場所を見つけやすくなります。
デプロイ方法の選択肢
(2026年10月現在)Rails を VPS に載せる方法は、大きく3つあります。
- Kamal:Rails 8 から新規アプリに設定ファイルが標準で生成されるデプロイツールです。アプリを Docker コンテナとして配布し、SSH 経由でサーバーへ展開します。Docker の知識が前提になります。
- Capistrano:Git からコードを取得し、リリースディレクトリを切り替えて配布する昔からの方式です。既存プロジェクトで多く使われています。
- 手動構成:この記事で扱う方法です。サーバー上に Ruby を入れ、systemd で Puma を動かします。台数が少なく、仕組みを把握したい場合に向いています。
どの方法でも、Puma がアプリを動かし、前段のプロキシが HTTPS を受ける点は同じです。
1. サーバーの準備と Ruby のインストール
OS の更新、SSH の鍵認証、ファイアウォール(SSH・HTTP・HTTPS のみ許可)を先に済ませます。
Ubuntu の標準パッケージの Ruby はバージョンが古いことがあり、アプリが指定するバージョンと合わないことが多いです。そこで rbenv や mise などのバージョン管理ツールで、アプリの .ruby-version に書かれたバージョンを入れます。Ruby はソースからビルドされるため、先にビルドに必要なライブラリを入れておきます。
sudo apt update
sudo apt install -y git curl build-essential libssl-dev libyaml-dev \
zlib1g-dev libffi-dev libreadline-dev nginx
libyaml-dev が無いと、Ruby のインストールは終わっても psych が使えず、Rails の起動時にエラーになることがあります。ビルドが途中で止まった場合は、エラーメッセージに出てくるライブラリ名を確認して、対応する -dev パッケージを追加してください。
アプリは専用のユーザーで動かし、rbenv もそのユーザーのホームに入れます。root や自分のログインユーザーで動かさないことで、アプリに脆弱性があったときの被害の範囲を狭められます。
sudo adduser --disabled-password myapp
sudo mkdir -p /srv/myapp && sudo chown myapp:myapp /srv/myapp
sudo -iu myapp # 以降の Ruby・アプリ関連の作業は myapp ユーザーで行う
rbenv の場合、公式の手順でインストールしたあと、次のようにバージョンを指定して入れます。
rbenv install 3.4.1 # .ruby-version に書かれたバージョンに合わせる
rbenv global 3.4.1
ruby -v
gem install bundler
2. データベースの用意
本番では PostgreSQL か MySQL を使うのが一般的です。Rails 8 は SQLite を本番でも使いやすくする改良が入っていますが、複数サーバーで動かす予定がある場合や、バックアップの運用を考えると、別のデータベースサーバーを選ぶ場面が多いでしょう。
PostgreSQL ならパッケージを入れ、アプリ用のユーザーとデータベースを作ります。libpq-dev は pg gem のビルドに必要です。
sudo apt install -y postgresql libpq-dev
sudo -u postgres createuser --pwprompt myapp
sudo -u postgres createdb -O myapp myapp_production
3. アプリの配置と本番用の準備
myapp ユーザーのまま、コードを取得して gem を入れます。
cd /srv/myapp
git clone https://github.com/your-name/myapp.git app
cd app
bundle config set --local deployment true
bundle config set --local without 'development test'
bundle install
deployment を有効にすると、Gemfile.lock に固定されたバージョンだけが入ります。手元とサーバーで gem のバージョンがずれて動作が変わるのを防ぐための設定です。
秘密情報(credentials)の扱い
Rails はパスワードや API キーを暗号化した config/credentials.yml.enc に保存し、その鍵を config/master.key か環境変数 RAILS_MASTER_KEY で渡します。master.key は Git にコミットせず、サーバーには権限を絞ったファイルや systemd の環境変数ファイルで配置します。データベースの接続情報も、DATABASE_URL などの環境変数で渡すとコードから分離できます。
# /etc/myapp.env(root 所有・myapp グループのみ読み取り可。値はダミー)
RAILS_ENV=production
RAILS_MASTER_KEY=your-master-key
DATABASE_URL=postgresql://myapp:your-db-password@127.0.0.1/myapp_production
アセットとマイグレーション
set -a; . /etc/myapp.env; set +a # 環境変数を読み込んでから実行
bin/rails assets:precompile
bin/rails db:migrate
assets:precompile が失敗する場合、JavaScript のビルドに Node.js が必要な構成(jsbundling-rails など)になっていないか確認します。Rails 7 以降の標準構成(importmap と Propshaft など)では Node.js が不要なこともあり、アプリによって前提が異なります。
4. Puma を systemd で常駐させる
# /etc/systemd/system/myapp.service
[Unit]
Description=myapp (Puma)
After=network.target postgresql.service
[Service]
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp.env
ExecStart=/home/myapp/.rbenv/shims/bundle exec puma -C config/puma.rb -b tcp://127.0.0.1:3000
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo journalctl -u myapp -n 50
ExecStart のパスは、rbenv をどのユーザーのどこに入れたかで変わります。which bundle で表示されるパスを指定してください。systemd はログインシェルの設定(.bashrc など)を読まないため、パスが通っていないことが起動失敗のよくある原因です。Puma のワーカー数やスレッド数は config/puma.rb と環境変数(WEB_CONCURRENCY、RAILS_MAX_THREADS)で調整します。メモリが少ない VPS では、ワーカーを増やしすぎないよう free -h で確認しながら決めます。
5. Nginx と HTTPS
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com;
root /srv/myapp/app/public;
location / {
try_files $uri @app;
}
location @app {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
root を public にして try_files を使うと、プリコンパイル済みのアセットは Nginx が直接返し、それ以外のリクエストだけが Puma に渡ります。設定後は sudo nginx -t で構文を確認してから sudo systemctl reload nginx で反映し、Certbot で証明書を取得します。
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
HTTPS 化後は、config/environments/production.rb の config.force_ssl と config.assume_ssl(Rails 7.1 以降)の設定を確認します。プロキシの後ろで動く Rails は、X-Forwarded-Proto を見て HTTPS かどうかを判断します。この値が渡っていないと、リダイレクトが繰り返されたり、Cookie の Secure 属性が付かなかったりします。また、本番環境ではホスト認可(config.hosts)で、公開するドメイン以外のホスト名でのアクセスを拒否できます。
6. 更新の流れ
更新は「データベースのバックアップ → git pull → bundle install → assets:precompile → db:migrate → sudo systemctl restart myapp」の順に行います。マイグレーションが失敗したときに戻せるよう、バックアップは毎回取ってください。この手順を繰り返すうちに作業が増えてきたら、Kamal や Capistrano への移行を検討する目安になります。
まとめ
- Ruby は rbenv などでアプリ指定のバージョンを入れ、ビルド用ライブラリ(特に
libyaml-dev)を先に用意する。 bundle config set --local deployment trueで、Gemfile.lockどおりの gem を入れる。master.keyは Git に入れず、環境変数ファイルで権限を絞って渡す。- Puma は systemd で
127.0.0.1に待ち受け、ExecStartにはbundleのフルパスを書く。 - Nginx で静的ファイルを返し、
X-Forwarded-Protoを渡してforce_ssl周りを確認する。