Deploying Ruby on Hop3¶
Deploying a Ruby web app on Hop3 means handing it a Gemfile, a Rack server, and a hop3.toml — Hop3 installs your gems with Bundler, boots your app under Puma bound to a dynamic port, and puts nginx in front of it. Whether you run a one-file Sinatra service or a full Rails 8 app with PostgreSQL and background jobs, the moving parts are the same; only the framework's conventions differ.
This page covers what every Ruby deployment has in common. Read it first, then follow the framework guide below that matches your app.
How Hop3 builds and runs Ruby¶
Hop3 detects a Ruby app from its Gemfile and uses the Ruby toolchain to build it:
- Install gems — Hop3 runs
bundle installfor you. You do not put this inbefore-build; the toolchain handles it. Usebefore-buildonly for steps that come after gems are installed, such asbin/rails assets:precompile. - Run extra build steps — anything in
[build] before-buildruns next (asset compilation, etc.). System packages your gems need to compile native extensions go in[build] packages(e.g.postgresql-dev,nodejs). - Start the process — Hop3 runs your
[run] startcommand. This must launch a Rack server (Puma in both guides) that binds to the$PORTenvironment variable Hop3 assigns — never a hard-coded port. Yourconfig/puma.rbshould read it:port ENV.fetch("PORT", 3000). - Proxy through nginx — Hop3 configures nginx to forward your app's public hostname (
HOST_NAME) to the process on$PORT. Because nginx terminates the request, disable Rack's host/origin protection (disable :protectionin Sinatra) so you don't get "Host not permitted" errors behind the proxy.
A representative Ruby hop3.toml:
[metadata]
id = "my-ruby-app"
version = "1.0.0"
[build]
# bundle install is automatic; list extra steps and system packages here
before-build = ["bin/rails assets:precompile"]
packages = ["postgresql-dev", "nodejs"]
[run]
start = "bundle exec puma -C config/puma.rb"
before-run = "bin/rails db:migrate" # runs once before the web process starts
[port]
web = 3000 # the port your app listens on locally; Hop3 maps it via $PORT
[healthcheck]
path = "/up"
[[provider]]
name = "postgres"
plan = "standard"
The [port] web value is the conventional port for the framework (3000 for Rails, 4567 for Sinatra); Hop3 still injects $PORT at runtime, so always read it in your Puma config rather than trusting the default.
Notes that apply to every Ruby framework¶
- Puma + a
config/puma.rb. Both guides runbundle exec puma -C config/puma.rb. Keep worker and thread counts driven by env vars (WEB_CONCURRENCY,RAILS_MAX_THREADS) so you can scale without code changes. - Health checks. Expose a cheap endpoint (Rails 8 ships
/up; Sinatra defines one) and point[healthcheck] pathat it so Hop3 knows when the app is live. - Addons inject env vars. Attach a
postgresprovider and Hop3 setsDATABASE_URL; attachredisand it setsREDIS_URL. Read those inconfig/database.ymland your job/cable config — never hard-code connection strings. - Migrations and pre-run hooks. Put schema migrations in
[run] before-run(or aprerun:line in aProcfile) so they run once per deploy before the web process boots. - Logs to stdout. Hop3 captures the process's stdout/stderr. For Rails, set
RAILS_LOG_TO_STDOUT=true; lethop3 app logs --app <app>show you everything. - Secrets via config, not in git. Declare
SECRET_KEY_BASE = { generate = "hex", length = 64 }inhop3.toml[env]so Hop3 generates it on the first deploy — never committed. Keepconfig/master.keyand credentials out of the repo; usehop3 config setfor secrets you supply yourself. - Procfile vs. hop3.toml. Either works for declaring processes; when both exist,
hop3.tomlwins. The guides show both styles — pick one and stay consistent.
Choose a framework¶
| Framework | Use it for |
|---|---|
| Rails | Full-stack Rails 8 apps — PostgreSQL via DATABASE_URL, asset precompilation, migrations, and optional Sidekiq/Solid Queue workers and Action Cable. |
| Sinatra | Lightweight services and JSON APIs — a single app.rb, a tiny Gemfile, Puma, and a config.ru for Rack. |
Jekyll, a Ruby static-site generator, is documented under Static Sites rather than here, since Hop3 serves its output directly through nginx with no running process.