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 周りを確認する。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲