> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Deploy Ruby applications

> How Ruby web applications build and run on Sevalla with Ingot: Bundler, Ruby versions, Puma on the PORT variable, and processes for migrations, jobs and workers.

A Ruby web application runs on Sevalla as an [application](https://docs.sevalla.com/applications/overview.md): Sevalla builds an image from your Git repository, then runs it as processes. The web process serves HTTP with Puma, background workers run job queues such as Solid Queue, and jobs run database migrations before each deployment.

## Framework guides

> **[Rails](https://docs.sevalla.com/guides/ruby/rails/overview.md)**
>
> Deploy Rails with PostgreSQL, then add background jobs, object storage and caching.

> **[Sinatra](https://docs.sevalla.com/guides/ruby/sinatra.md)**
>
> Deploy a Sinatra application with Puma and `config.ru`.

Moving from Heroku? See [Migrate from Heroku](https://docs.sevalla.com/guides/migrate-from-heroku.md).

## How Ruby applications run on Sevalla

| Part | On Sevalla |
| - | - |
| Build | [Ingot](https://docs.sevalla.com/applications/builds/ingot.md), Sevalla's builder, by default. It recognizes a Ruby application by its `Gemfile`, and needs the `Gemfile.lock` next to it. It builds Rails, Hanami, Sinatra and any other Rack application. |
| Package manager | Bundler, the release that wrote `Gemfile.lock` (its `BUNDLED WITH` line). Ingot installs the gems into `/app/vendor/bundle` with `BUNDLE_DEPLOYMENT` set, so the build fails if `Gemfile.lock` doesn't match the `Gemfile`, and skips the `development` and `test` groups. Gem executables, such as `puma`, are on the `PATH`, in custom start commands too. |
| Ruby version | Ruby 4.0, Ingot's default line, unless the project sets another. Pin it in the `Gemfile`: see [Pin the Ruby version](#pin-the-ruby-version). |
| Web server | For Rails, `bin/rails server --binding 0.0.0.0`, which runs Puma. For other Rack applications, Puma with the `config.ru` in the build path: `bundle exec puma --bind "tcp://0.0.0.0:$PORT"`, or `rackup` when the `Gemfile` has the `rackup` gem and another server, such as Falcon or Thin, instead. The server must be in the `Gemfile`. |
| Port | Sevalla sets `PORT` to the web process's port, `8080` by default. `bin/rails server` reads it; for other Rack applications, Ingot passes it to Puma. |
| Concurrency | Puma runs threads in 1 process: 3 in Rails (`RAILS_MAX_THREADS`), up to 5 otherwise (`MAX_THREADS`). On a larger size, set the `WEB_CONCURRENCY` environment variable to the number of Puma processes, such as `2`. Ingot's image preloads jemalloc, which keeps the memory of threaded Ruby servers from fragmenting. |
| Start command | What Ingot detects, shown in the build plan's **Start** row. To run something else, add a `web` entry to a `Procfile`, set `start` in `ingot.json`, or set the web process's **Custom start command** on the **Processes** tab. See [Set the start command](https://docs.sevalla.com/applications/builds/ingot.md#set-the-start-command). |
| Assets | For Rails, Ingot runs `bootsnap precompile` and `bin/rails assets:precompile` during the build, with `SECRET_KEY_BASE_DUMMY=1`, so the real secret isn't needed. It adds Node.js to the build when the assets need it, for example with `jsbundling-rails` or `cssbundling-rails`. |
| System libraries | Ingot adds the Debian packages the locked gems load at runtime, such as `libvips` for `ruby-vips` and `libpq5` for `pg` when it isn't locked with its Linux builds, and fails the build if a compiled gem needs a library the image lacks. Add others with `packages.run` in `ingot.json`. |
| Migrations | A job with the start policy **Before deployment**, for example `bin/rails db:prepare`. If it fails, the deployment fails and the previous version keeps running. Ingot doesn't run migrations, except for a Rails application on SQLite: its database is on the web process's disk, so the web process runs `bin/rails db:prepare` before it starts. |
| Job queues | A background worker, for example `exec bin/jobs` for Solid Queue. Start the command with `exec`: Sevalla runs it with `/bin/sh -l -c`, and `exec` makes the queue the container's main process, so it receives `SIGTERM` and returns unfinished jobs to the queue on each deployment. See [Run Rails background jobs](https://docs.sevalla.com/guides/ruby/rails/background-jobs.md). |
| Scheduled tasks | Solid Queue's recurring tasks, or a cron job, for example `bin/rails runner "Cart.expired.destroy_all"` every night. |
| Databases | PostgreSQL, MySQL, MariaDB, Redis or Valkey on Sevalla, reached over an [internal connection](https://docs.sevalla.com/databases/connect.md#connect-an-application) that adds the connection details as environment variables. Rails connects to the PostgreSQL URL when you name it `DATABASE_URL`. |
| Files | The filesystem resets on every deployment. Keep uploads in [object storage](https://docs.sevalla.com/object-storage/overview.md) with Active Storage, or attach a [disk](https://docs.sevalla.com/applications/disks.md). |
| Other build strategies | [Dockerfile](https://docs.sevalla.com/applications/builds/dockerfile.md), [Railpack](https://docs.sevalla.com/applications/builds/railpack.md), [Buildpacks](https://docs.sevalla.com/applications/builds/buildpacks.md) and the deprecated [Nixpacks](https://docs.sevalla.com/applications/builds/nixpacks.md), on the application's **Settings** tab |

Every process (web, worker, cron job and job) runs from the same image, each with its own start command. See [Processes](https://docs.sevalla.com/applications/processes.md) for every process type and its settings.

Ingot stops the build with a message in the build log when Bundler can't install the gems as committed:

| Build log | Fix |
| - | - |
| `Gemfile.lock is missing` | Run `bundle lock` and commit `Gemfile.lock`. |
| `Gemfile.lock lists no Linux platform, so Bundler won't install it in a Linux image` | Run `bundle lock --add-platform x86_64-linux aarch64-linux` and commit `Gemfile.lock`. |
| `No web server gem found` | Add Puma with `bundle add puma`, or set the start command. |

## Pin the Ruby version

Ingot builds on Ruby 4.0, its default line, unless the project sets a version. Pin the line you develop with in the `Gemfile`, under the `source` line, so the build doesn't change when a new Ingot release moves its default line:

```ruby Gemfile theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
ruby "~> 4.0.0"
```

Then run `bundle install` to record it in `Gemfile.lock`. `~> 4.0.0` allows any 4.0 release: Ingot builds on the 4.0 line's image, which moves to each new patch release, and Bundler refuses to run the application on another line.

Ingot takes the version from the first of these that's set:

1. The `INGOT_RUBY_VERSION` environment variable, or `versions.ruby` in `ingot.json`.
2. The `ruby` line of the `Gemfile`, or the file it names with `ruby file: ".ruby-version"`.
3. `RUBY VERSION` in `Gemfile.lock`, which records the version when the `ruby` line computes it, as `ruby File.read(".ruby-version")` does.
4. `.ruby-version`, then `.tool-versions` or mise's configuration.

The build plan's **Ruby** row shows the version and where it came from. An exact release, such as the `ruby-4.0.9` that `rails new` writes to `.ruby-version`, builds on that release only, without later security fixes; with the `ruby` line in the `Gemfile`, Ingot doesn't read `.ruby-version`. Ingot builds with the standard Ruby interpreter: a `Gemfile` that names another engine, such as JRuby, fails the build. See [Set the language version](https://docs.sevalla.com/applications/builds/ingot.md#set-the-language-version).

## Build with a Dockerfile

Ingot needs no Dockerfile. When you want full control of the image, a Dockerfile for a Ruby application builds the gems in one stage, from `Gemfile.lock` with `BUNDLE_DEPLOYMENT` set, and copies them with the code into a final stage on `ruby:4.0-slim-trixie`, the Ruby line the `Gemfile` pins. The final stage keeps the code owned by root, runs as a non-root user with UID 1000, and starts Puma in exec form so it receives `SIGTERM` and finishes its requests, listening on `PORT` (`8080` by default). Rails generates a Dockerfile with `rails new`: see [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md#deploy-with-a-dockerfile) for what to change for Sevalla, and [Deploy a Sinatra application](https://docs.sevalla.com/guides/ruby/sinatra.md#deploy-with-a-dockerfile) for a Rack application.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.