{
  "manifest_version": "1",
  "slug": "rails",
  "title": "Ruby on Rails",
  "headline": "Deploy a Rails app",
  "description": "Deploy a Rails 8 app to an exe.dev VM: git push to deploy, HTTPS, SQLite and Solid Queue on a persistent disk, nightly backups, no Docker needed.",
  "category": "frameworks",
  "tags": [
    "rails hosting",
    "deploy rails",
    "rails 8",
    "sqlite in production",
    "solid queue",
    "thruster",
    "puma",
    "git push deploy",
    "heroku alternative"
  ],
  "url": "https://exe.dev/deploy/rails",
  "markdown_url": "https://exe.dev/deploy/rails.md",
  "upstream": {
    "website": "https://rubyonrails.org",
    "source": "https://github.com/rails/rails",
    "docs": "https://guides.rubyonrails.org",
    "license": "MIT"
  },
  "example_vm_name": "my-rails",
  "time": "10 minutes",
  "stack": [
    {
      "name": "Thruster and Puma",
      "detail": "Rails 8's production server, run by systemd as app.service",
      "port": 8000
    },
    {
      "name": "Ruby 4",
      "detail": "Installed with mise; your app's .ruby-version wins"
    },
    {
      "name": "SQLite",
      "detail": "The app's database, plus Solid Cache, Solid Queue and Solid Cable",
      "data": "~/data"
    },
    {
      "name": "Solid Queue",
      "detail": "Background and recurring jobs, inside Puma"
    },
    {
      "name": "git push deploys",
      "detail": "A bare repository whose hook builds, migrates and restarts",
      "data": "~/app.git"
    },
    {
      "name": "Nightly backups",
      "detail": "Consistent SQLite copies, uploads and app.env; two weeks kept",
      "data": "~/backups"
    }
  ],
  "access": [
    {
      "url": "https://my-rails.exe.xyz/",
      "who": "private",
      "what": "Your app, until you make it public"
    },
    {
      "url": "ssh my-rails.exe.xyz",
      "who": "private",
      "what": "`git push exe main`, a shell, logs and the Rails console"
    }
  ],
  "inputs": [
    {
      "name": "REPO",
      "description": "the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh Rails app"
    }
  ],
  "delivers": [
    "Your app from GitHub (REPO), or a new Rails 8 app with a Notes scaffold, in production as `app.service`, with Thruster on port 8000 in front of Puma on localhost, enabled to come back after a reboot.",
    "A bare repository at `~/app.git` whose pre-receive hook deploys every push to main (bundle install, assets:precompile, a backup, db:prepare, restart, health check); a failed deploy rejects the push and the running version stays.",
    "SQLite databases and uploads in `~/data`, outside the code; settings and secrets (with `RAILS_MASTER_KEY` for a new app) in `~/app.env`, mode 600.",
    "For a new app, `config.assume_ssl` and `config.force_ssl` on, so forms, cookies and redirects work behind exe.dev's HTTPS.",
    "A nightly backup timer, and a backup in `~/backups` unpacked in a scratch directory whose main database passes an integrity check."
  ],
  "owner_runs_after": [
    "git clone -o exe my-rails.exe.xyz:app.git    # if you started fresh",
    "git remote add exe my-rails.exe.xyz:app.git  # if you brought your app: in your checkout"
  ],
  "deployable": true,
  "deploy": {
    "new_vm_url": "https://exe.dev/new?prompt=REPO%3D%3Cfill-this-in%3E%0A%0ASet%20up%20Ruby%20on%20Rails%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe.dev%2Fdeploy%2Frails.md%20from%20top%20to%20bottom.%0A%0AThe%20values%20at%20the%20top%20of%20this%20message%20are%20mine%3A%0A-%20REPO%3A%20the%20GitHub%20repository%20to%20deploy%2C%20as%20owner%2Fname%20or%20a%20URL%2C%20or%20%60new%60%20to%20start%20a%20fresh%20Rails%20app%0AAsk%20me%20for%20any%20that%20still%20say%20%3Cfill-this-in%3E%20before%20you%20need%20them.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-rails.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-rails.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Your%20app%20from%20GitHub%20%28REPO%29%2C%20or%20a%20new%20Rails%208%20app%20with%20a%20Notes%20scaffold%2C%20in%20production%20as%20%60app.service%60%2C%20with%20Thruster%20on%20port%208000%20in%20front%20of%20Puma%20on%20localhost%2C%20enabled%20to%20come%20back%20after%20a%20reboot.%0A-%20A%20bare%20repository%20at%20%60~%2Fapp.git%60%20whose%20pre-receive%20hook%20deploys%20every%20push%20to%20main%20%28bundle%20install%2C%20assets%3Aprecompile%2C%20a%20backup%2C%20db%3Aprepare%2C%20restart%2C%20health%20check%29%3B%20a%20failed%20deploy%20rejects%20the%20push%20and%20the%20running%20version%20stays.%0A-%20SQLite%20databases%20and%20uploads%20in%20%60~%2Fdata%60%2C%20outside%20the%20code%3B%20settings%20and%20secrets%20%28with%20%60RAILS_MASTER_KEY%60%20for%20a%20new%20app%29%20in%20%60~%2Fapp.env%60%2C%20mode%20600.%0A-%20For%20a%20new%20app%2C%20%60config.assume_ssl%60%20and%20%60config.force_ssl%60%20on%2C%20so%20forms%2C%20cookies%20and%20redirects%20work%20behind%20exe.dev%27s%20HTTPS.%0A-%20A%20nightly%20backup%20timer%2C%20and%20a%20backup%20in%20%60~%2Fbackups%60%20unpacked%20in%20a%20scratch%20directory%20whose%20main%20database%20passes%20an%20integrity%20check.%0A%0ARead%20the%20whole%20recipe%2C%20including%20the%20closed%20details%2C%20and%20follow%20%22Install%0Aby%20hand%22%20in%20order%2C%20skipping%20VM%20creation.%20The%20walkthrough%20above%20it%20is%20for%0Athe%20owner%20afterwards.%20Ask%20for%20REPO%20first%20if%20it%20says%20%3Cfill-this-in%3E.%0AFor%20REPO%3Dnew%2C%20follow%20%22Start%20a%20new%20app%22%20exactly.%20For%20a%20repository%2C%20follow%0A%22Bring%20your%20app%22%3A%20adapt%20~%2Fapp.env%2C%20apt%20packages%20and%20app.service%20to%20the%0Aapp%20as%20its%20checklist%20says%2C%20but%20never%20commit%20to%20the%20owner%27s%20code%2C%20in%0AGitHub%20or%20in%20~%2Fapp.git%2C%20so%20their%20first%20push%20fast-forwards.%20If%20a%20private%0Arepository%20can%27t%20be%20cloned%2C%20give%20the%20owner%20the%20%60integrations%20add%20github%60%0Acommand%20with%20this%20VM%27s%20name%20and%20wait%20for%20them.%0AKeep%20the%20deploy%20hook%20and%20backup%20script%20as%20written%2C%20since%20the%20page%0Adocuments%20how%20they%20behave%3B%20if%20one%20looks%20wrong%2C%20say%20so%20at%20the%20end.%0ADon%27t%20reboot%2C%20don%27t%20make%20the%20VM%20public%2C%20and%20don%27t%20run%20the%20Restore%20steps%3B%0Atest%20the%20backup%20in%20a%20scratch%20directory%20as%20written.%0AFinish%20with%20the%20app%27s%20URL%3B%20only%20the%20one%20git%20command%20from%20%60after%60%20that%0Afits%20%28%60git%20clone%20-o%20exe%60%20for%20REPO%3Dnew%2C%20%60git%20remote%20add%20exe%60%20for%20a%0Arepository%29%3B%20what%20you%20changed%20for%20their%20app%3B%20and%2C%20if%20it%20has%20a%0Aconfig%2Fcredentials.yml.enc%2C%20the%20command%20from%20%22Settings%20and%20secrets%22%20that%0Asends%20its%20master%20key.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open%20and%20these%20commands%2C%20which%20only%20I%20can%20run%2C%20from%20my%20own%20computer%20%28with%20this%20VM%27s%20name%20in%20them%29%3A%0A%20%20git%20clone%20-o%20exe%20my-rails.exe.xyz%3Aapp.git%20%20%20%20%23%20if%20you%20started%20fresh%0A%20%20git%20remote%20add%20exe%20my-rails.exe.xyz%3Aapp.git%20%20%23%20if%20you%20brought%20your%20app%3A%20in%20your%20checkout%0A",
    "terminal": "curl -fsSL https://exe.dev/deploy/rails/prompt.txt | ssh exe.dev new --prompt=/dev/stdin",
    "prompt": "REPO=\u003cfill-this-in\u003e\n\nSet up Ruby on Rails on this VM by following the exe.dev recipe at https://exe.dev/deploy/rails.md from top to bottom.\n\nThe values at the top of this message are mine:\n- REPO: the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh Rails app\nAsk me for any that still say \u003cfill-this-in\u003e before you need them.\n\nThe recipe's example VM name is my-rails. This VM's name is the output of `hostname`: use it wherever the recipe writes my-rails. Skip the step that creates the VM; you are on it.\n\nYou are done when all of these are true. Check each one yourself:\n- Your app from GitHub (REPO), or a new Rails 8 app with a Notes scaffold, in production as `app.service`, with Thruster on port 8000 in front of Puma on localhost, enabled to come back after a reboot.\n- A bare repository at `~/app.git` whose pre-receive hook deploys every push to main (bundle install, assets:precompile, a backup, db:prepare, restart, health check); a failed deploy rejects the push and the running version stays.\n- SQLite databases and uploads in `~/data`, outside the code; settings and secrets (with `RAILS_MASTER_KEY` for a new app) in `~/app.env`, mode 600.\n- For a new app, `config.assume_ssl` and `config.force_ssl` on, so forms, cookies and redirects work behind exe.dev's HTTPS.\n- A nightly backup timer, and a backup in `~/backups` unpacked in a scratch directory whose main database passes an integrity check.\n\nRead the whole recipe, including the closed details, and follow \"Install\nby hand\" in order, skipping VM creation. The walkthrough above it is for\nthe owner afterwards. Ask for REPO first if it says \u003cfill-this-in\u003e.\nFor REPO=new, follow \"Start a new app\" exactly. For a repository, follow\n\"Bring your app\": adapt ~/app.env, apt packages and app.service to the\napp as its checklist says, but never commit to the owner's code, in\nGitHub or in ~/app.git, so their first push fast-forwards. If a private\nrepository can't be cloned, give the owner the `integrations add github`\ncommand with this VM's name and wait for them.\nKeep the deploy hook and backup script as written, since the page\ndocuments how they behave; if one looks wrong, say so at the end.\nDon't reboot, don't make the VM public, and don't run the Restore steps;\ntest the backup in a scratch directory as written.\nFinish with the app's URL; only the one git command from `after` that\nfits (`git clone -o exe` for REPO=new, `git remote add exe` for a\nrepository); what you changed for their app; and, if it has a\nconfig/credentials.yml.enc, the command from \"Settings and secrets\" that\nsends its master key.\n\nFinish with the URLs I should open and these commands, which only I can run, from my own computer (with this VM's name in them):\n  git clone -o exe my-rails.exe.xyz:app.git    # if you started fresh\n  git remote add exe my-rails.exe.xyz:app.git  # if you brought your app: in your checkout\n",
    "prompt_url": "https://exe.dev/deploy/rails/prompt.txt"
  },
  "updated": "2026-10-17",
  "verified": "2026-10-17",
  "related": [
    "laravel",
    "postgres",
    "migrate-heroku",
    "django",
    "docker-compose",
    "fastapi"
  ],
  "alternatives": [
    "Heroku",
    "Render",
    "Fly.io"
  ]
}
