Skip to content

Ruby

Start from a Ruby template in the wizard or pick Ruby application for an empty project or a repository, or add Ruby to any project in the Runtime section. The container, envoryx-<project>-ruby, runs ghcr.io/envoryx/envoryx-ruby:<version>. That’s the official ruby:<version>-slim-bookworm image plus what the common native gems need to compile (pg, mysql2, sqlite3, psych), libvips for the image variants of Active Storage’s default :vips processor, and the debug gem. It runs as PUID:PGID, with your project at /var/www/html and a home directory at /home/envoryx.

Gems are installed into GEM_HOME=/home/envoryx/.gem/ruby/<X.Y.0>, so ~/.gem/ruby/3.4.0 for Ruby 3.4. That’s the directory RubyGems itself uses for user installs, which is why gems an IDE installs there (RubyMine’s debugger gems, for example) are found without any GEM_PATH tricks, and gems built for one Ruby version never meet another. They survive when the container is recreated, and your project folder stays free of a vendor/bundle. Bundler’s download cache sits in the project’s package cache, so reinstalling the bundle doesn’t download the gems again. With sharing switched on there, all projects use one cache, and the second Rails app installs a lot faster than the first.

Projects from before Envoryx 0.17.0 used ~/.gem/ruby without the version. They install their bundle once more on the first restart after the update, and the old contents of ~/.gem/ruby in the project home can be deleted.

There’s no Node.js in the image. If you use jsbundling-rails or cssbundling-rails, add a Node.js service next to Ruby for those builds. The Rails template uses importmap, which doesn’t need Node at all.

4.0 is the default, 3.4 and 3.3 are available too.

Switch on Run the Ruby server and pick a preset. The server becomes the container’s main process, restarts automatically and gets a host port of its own. Switch the server off and on again and it keeps that port, so a bookmark to it stays valid.

The Ruby card in the Runtime section with the version, the Rails server switched on, the mode, the port and the rdbg optionThe Ruby card in the Runtime section with the version, the Rails server switched on, the mode, the port and the rdbg option
Preset Development Production Port
Rails bin/rails server - Rails reloads changed code itself bundle exec puma 3000
Rack Puma on config.ru (Sinatra, Roda, Hanami …) the same 9292

The server listens on $PORT, with HOST and BINDING set to 0.0.0.0. RAILS_ENV, RACK_ENV, APP_ENV and HANAMI_ENV follow the mode, and your workers get the same values. With the Rack preset, reloading changed code is up to the framework - sinatra/reloader, for example.

Rails refuses requests for host names it doesn’t know. In development, Envoryx sets RAILS_DEVELOPMENT_HOSTS=.<base>, so your project’s names get through without you touching config.hosts.

Production mode really is Rails’ production environment, with everything that implies. It needs config/master.key (or SECRET_KEY_BASE as a project variable), precompiled assets - run rails assets:precompile - and the databases your database.yml lists for production. Rails then serves public/ itself, and if your production config has force_ssl, it redirects to https.

Before the server starts, it waits for the Gemfile (and bin/rails or config.ru). Then it runs bundle check, and if anything is missing - after a fresh clone, a changed Gemfile or a Ruby upgrade - bundle install. So a cloned application simply comes up, without you running anything by hand. If the install fails, the log says so and it tries again every 30 seconds. Workers do the same before they start.

That check happens at start, though. A server that’s already running keeps the bundle it started with, so after a bundle add or a Gemfile edit, restart the project. That brings up the server and the workers with the new gems; until then you’ll see a LoadError for the new gem.

Envoryx injects DATABASE_URL, and Rails merges it into config/database.yml on its own. For PostgreSQL, the Ruby containers get it as postgresql://…, the form Active Record understands. An additional database called analytics shows up as ANALYTICS_DATABASE_URL, and Rails’ multi-database setup picks it up for a database named analytics in database.yml.

Solid Queue gets a database of its own, like Rails sets it up for production. The Ruby containers receive QUEUE_DATABASE_URL, pointing at <database>_queue on the project’s database server, and Rails merges it into the queue entry of database.yml. Rails 8 writes that entry for production; the Rails templates add one for development too, and a cable entry next to it. As soon as database.yml has a queue entry, Envoryx creates the queue database (as the administrator, like the test database), and rails db:prepare loads Solid Queue’s tables into it.

Solid Cache and Solid Cable work the same way. CACHE_DATABASE_URL points at <database>_cache and CABLE_DATABASE_URL at <database>_cable, for the cache and cable entries Rails 8 writes for production, and Envoryx creates those databases when database.yml has the entries. Ruby projects from before 0.18 report an older setup once because of the two new variables; restart them when it suits you.

The Rails template leaves the cache in development on :memory_store. If you want Solid Cache there too, add a cache entry to the development section of database.yml (like the one for production, with migrations_paths: db/cache_migrate), point config/cache.yml at it with database: cache, set config.cache_store = :solid_cache_store in development.rb and run rails db:prepare.

These databases belong to the project like the primary one. Backups and restores include the _queue, _cache and _cable databases, a duplicate and a restore into a new project bring them along, and a rename moves them to the new name. So pending Solid Queue jobs survive a restore, and a copy doesn’t fail on a missing queue database.

Without PHP and without a Python or Go server, the Ruby server is the application: https://<project>.<base> and every extra domain go straight to it, the web container’s port stays closed, and Direct access is the Ruby host port. Next to PHP, a Python or a Go server it only has its own host port. A Node.js dev server next to Ruby keeps <project>-dev.<base>.

Rails runs rails new with Hotwire and importmap, Rails (API only) runs rails new --api. Both are named after your project and set up for its database - PostgreSQL, MySQL or MariaDB, and SQLite if the project has none. The first time takes a few minutes while the native gems compile. If bundle install fails along the way, say for lack of network or a gem that won’t build, the template fails with the reason instead of leaving half an application behind. Once the project is up, Envoryx runs rails db:prepare for you; if you created it stopped, run that from the Actions section before you open the site.

The templates also set up the development environment for working under Envoryx:

  • Solid Queue gets its own queue database and Solid Cable its own cable database (see Database), and Action Cable uses Solid Cable in development instead of the async adapter. With async, a Turbo broadcast from a job never leaves the worker process, so it never reaches the browser. For background jobs you add the Solid Queue worker afterwards.
  • lib/tasks/envoryx.rake makes rails db:prepare load the queue and cable schemas into a database that has nothing but Rails’ bookkeeping tables. Opening the site before the first db:prepare creates those tables, and db:prepare would otherwise take the queue database as done.
  • With Mailpit in the project, Action Mailer delivers to it. Without that setting Rails tries localhost:25 and silently drops every mail. Links in mails (post_url in a mailer view) point at the project address from ENVORYX_URL, not at Rails’ default localhost:3000.
  • The server and the workers log to stdout as well, so the Logs section shows them; log/development.log stays.
  • web-console accepts the networks of the container. Its console still doesn’t open on an error page you reach through the project URL: web-console checks the browser’s address from X-Forwarded-For, and that’s your machine, not the proxy. Envoryx leaves it at that on purpose, because the console runs any Ruby code, and opening it to your whole private network would hand that to everyone there. Use rails console in the Ruby terminal instead, or, if you accept the risk, add your own address to config.web_console.allowed_ips in development.rb.

Sinatra writes an app.rb, a config.ru and a Gemfile with Puma, sinatra-contrib for the reloader and the debug gem.

With object storage in the project, Active Storage can use it as an S3 service. Add aws-sdk-s3 to the Gemfile and a service to config/storage.yml:

envoryx:
service: S3
endpoint: <%= ENV["S3_ENDPOINT"] %>
access_key_id: <%= ENV["S3_ACCESS_KEY"] %>
secret_access_key: <%= ENV["S3_SECRET_KEY"] %>
region: <%= ENV["S3_REGION"] %>
bucket: <%= ENV["S3_BUCKET"] %>
force_path_style: true

Then set config.active_storage.service = :envoryx. By default Rails sends the browser to a URL signed for S3_ENDPOINT, http://s3:9000, which only exists inside the project network. The simplest way out is config.active_storage.resolve_model_to_route = :rails_storage_proxy: Rails then streams the files itself under the project URL.

The Tests section runs bundle exec rspec when there’s a spec/ folder and rspec in Gemfile.lock, and bin/rails test. A Rails app with rspec-rails gets both suites offered, so you pick the one you use. Both run in the test environment against <database>_test on your project’s database server.

That last part matters. Active Record merges DATABASE_URL into whatever environment is running, so a test run would otherwise load its fixtures into your development database and empty its tables. Envoryx points the run at the test database instead and creates that database first. It does so as the administrator, because the MySQL and MariaDB project login isn’t allowed to create databases. Rails then loads the schema. If rspec_junit_formatter is in your bundle, failures show up per test. rails test writes no report, so Envoryx reads its summary line and the failures Rails prints (test, message, file and line) from the output, and you get counts and failed tests there too.

Debug with rdbg runs the server under rdbg --open --nonstop from the debug gem, on port 12345 unless you change it, and publishes that port on a host port. When Gemfile.lock locks the debug gem

  • Rails and the Sinatra template do - the rdbg from your bundle is used, so there’s only one version of the gem in play.

In VS Code, install the VSCode rdbg Ruby Debugger extension and add this, with host and port from the IDE section:

.vscode/launch.json
{
"type": "rdbg",
"name": "Attach to Envoryx",
"request": "attach",
"debugPort": "<host>:<port>",
"localfsMap": "/var/www/html:${workspaceFolder}"
}

From a terminal, rdbg -A <host> <port> does the same.

rdbg takes one client at a time. If an attach doesn’t come back, an earlier session is probably still connected, for example an rdbg -A in the Ruby terminal you closed without quitting it. End that process (pkill -f 'rdbg -A' in the Ruby terminal) and attach again.

RubyMine is different. Its Ruby remote debug configuration only speaks ruby-debug-ide, not the debug gem, so it can’t attach to rdbg. Set up the Ruby container as an SSH remote interpreter instead (user <project>.ruby, see IDE integration), and RubyMine debugs with its own debugger. You don’t need the rdbg switch for that, but you do need Allow JetBrains Gateway for this project in the IDE section, because RubyMine’s debugger forwards a port over SSH.

Without the server, only the port is published - for an rdbg you start yourself in the Ruby terminal, such as rdbg --open --host=0.0.0.0 --port=12345 -c -- bin/rails test.

The Actions section has ruby --version, bundle install, bundle update, bundle outdated, rubocop (when there’s a .rubocop.yml), and for Rails db:prepare, db:migrate, db:rollback, db:seed, routes, assets:precompile, tmp:clear and about.

For background jobs there are workers for Solid Queue (bin/jobs start), GoodJob (good_job start, optionally limited to some queues), Sidekiq (optionally with queues - add Redis to the project and REDIS_URL is injected), a Rake task and a Ruby script. A Solid Queue worker whose tables don’t exist yet waits with a hint in its log until rails db:prepare has run. The recurring tasks from config/recurring.yml run inside that same worker, so they don’t need a second one. More under Workers and cron jobs.

The Ruby card in the Runtime section has an Enable Ruby switch. Turning it off removes the Ruby container and the containers of the Ruby workers; your files and the worker definitions stay. On the command line, envoryx takes --ruby <version>, --ruby-server and --ruby-preset rails|rack.