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

# Ruby and Rails deploy guide

> Configure your Ruby or Ruby on Rails application for production on Laravel Cloud.

The [Ruby and Rails quickstart](/docs/quickstart#ruby-and-rails) walks you through your first deployment to Laravel Cloud. Once your application is up and running, this guide will help you get it ready for production. We'll cover preparing your repository, running Puma, connecting your databases, and handling assets, uploads, and background jobs. Most of the guide focuses on Ruby on Rails, but if you are deploying another Ruby or Rack application, be sure to read [Other Ruby and Rack applications](#other-ruby-and-rack-applications) as well.

## Prepare your repository

Cloud detects a Ruby application when it finds a `Gemfile` in your application directory. You should commit both your `Gemfile` and `Gemfile.lock`, and make sure they include your production server and database adapter. For Rails applications, keep the generated `config/application.rb`, `config/environment.rb`, and `bin/rails` files in your repository, since Cloud uses them to recognize the framework.

Your application runs on Linux with ARM64 processors. If your lockfile only lists the platforms for your development machine, add Cloud's platform locally and commit the updated lockfile:

```sh theme={null}
bundle lock --add-platform aarch64-linux
```

Cloud accepts lockfiles that include the `aarch64-linux`, `aarch64-linux-gnu`, or generic `ruby` platform. If the application creation form reports a missing platform, push your updated lockfile and refresh the repository scan before continuing.

If your application lives in a [monorepo](/docs/monorepos), select the directory that contains your `Gemfile`. Cloud keeps the surrounding repository available, so dependencies such as sibling path gems continue to work.

## Choose a Ruby version

Cloud supports Ruby 3.2, 3.3, 3.4, and 4.0, with Ruby 3.4 as the default. The simplest way to choose your version is with a `.ruby-version` file:

```text theme={null}
3.4
```

Cloud reads your version declaration from each deployment's commit. It checks `.ruby-version` first, then the `RUBY VERSION` section of `Gemfile.lock`, the `ruby` declaration in your `Gemfile`, `.tool-versions`, and finally `mise.toml`. A pinned version selects a major and minor release line, while a `Gemfile` version constraint selects the lowest supported release line that satisfies it.

If your repository doesn't declare a version, you may select one from your environment's **General Settings** instead. If you declare a version Cloud doesn't support, the deployment stops before building. You can find more details in the [Ruby runtime](/docs/runtimes#ruby) documentation.

It's a good idea to keep your Ruby declarations consistent with one another. In particular, if your `Gemfile` requires an exact patch version, it must match the Ruby patch version Cloud installs, or Bundler will reject the build.

## Install dependencies

Cloud installs your gems automatically before running your [build commands](/docs/environments#build-commands). It uses the Bundler version recorded under `BUNDLED WITH` in your `Gemfile.lock`, or the Bundler version that ships with the runtime if that section is missing.

Gems are installed using Bundler's deployment and frozen modes, so you should update your dependencies locally and commit the lockfile rather than running `bundle update` during deployment. Be sure that every gem your application needs in production lives outside of the excluded groups, including `puma`, your database adapter, and any gems used for background jobs or building assets.

By default, Cloud skips the `development` and `test` groups. You may change which groups are installed using your [environment variables](/docs/environments#environment-variables) or [linked secrets](/docs/secrets):

```ini theme={null}
BUNDLE_WITHOUT=development:test
BUNDLE_WITH=production
```

If you need every ordinary group installed, set `BUNDLE_WITHOUT` to an empty value. Cloud applies these settings while installing your gems and keeps the same group configuration at runtime. Remember to redeploy after changing them.

## Run a production server

### Rails

Cloud pre-fills your Rails start command using your application's binstub, when it exists:

```sh theme={null}
bundle exec bin/rails server -b '::' -p $PORT
```

If your application doesn't have a `bin/rails` binstub, Cloud uses `bundle exec rails server` with the same bind and port options. Either way, keep `puma` in your production dependencies so Rails can start its server.

Your server must listen on the port set by the `PORT` environment variable, which is `3000` by default, and it must accept IPv6 connections. This matters because Cloud's startup probes reach your application over IPv6. If you change the start command, be sure to keep the `::` bind, since an IPv4-only or loopback-only listener will prevent it from reaching your application.

If you would rather start Puma directly, replace the `port` or `bind` directive in your `config/puma.rb` file with the following:

```ruby theme={null}
bind "tcp://[::]:#{ENV.fetch('PORT', '3000')}"
```

Then, set your start command to:

```sh theme={null}
bundle exec puma -C config/puma.rb
```

Puma should have a single listener for your application's port, so remove any other `port` or `bind` directives from your configuration. You should also keep Puma running in the foreground.

### Procfile commands

If your repository includes a `Procfile`, Cloud uses its `web` command as your initial start command instead of the framework default. For example:

```text theme={null}
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq
release: bin/rails db:prepare
```

If you use this example, remember to configure the IPv6 listener in `config/puma.rb` as described in the previous section. For Rails applications, Cloud also offers the `release` command in your deployment settings, and your other processes as background process suggestions. It doesn't run your entire Procfile automatically, so be sure to review these suggestions and configure the ones you need in your environment.

## Rails environment variables

New Rails environments include a securely generated `SECRET_KEY_BASE`, which you are free to edit. You should keep this value the same across deployments so your users' existing signed cookies and sessions stay valid. If you are migrating an existing application, use its current secret.

Cloud also provides the following production defaults:

| Variable | Value |
| - | - |
| `RAILS_ENV` | `production` |
| `RACK_ENV` | `production` |
| `BUNDLE_WITHOUT` | `development:test` |
| `DISABLE_SPRING` | `1` |
| `RAILS_LOG_TO_STDOUT` | `1` |

Cloud sets your environment variables on your application's process and makes them available to your build commands, so you can read any custom settings from `ENV` just like you do locally. If you define a variable yourself, your value takes precedence over both linked secrets and the injected defaults.

### Encrypted credentials

If your application uses encrypted Rails credentials, add the matching `RAILS_MASTER_KEY` to your environment variables or [Secrets Manager](/docs/secrets). Be sure to use the key for the credentials file your production configuration actually loads, such as `config/credentials.yml.enc` or `config/credentials/production.yml.enc`.

Keep in mind that `SECRET_KEY_BASE` is only used to sign your application's data. It can't decrypt your credentials, so it doesn't replace `RAILS_MASTER_KEY`. If your production configuration explicitly enables `config.require_master_key`, Cloud will check for a key before building your application.

## Databases

When you connect a [Postgres](/docs/resources/databases/postgres) or [MySQL](/docs/resources/databases/laravel-mysql) database from your environment's infrastructure canvas, Cloud provides a `DATABASE_URL` variable containing a `postgresql://` or `mysql2://` URL for Rails.

Be sure to include the `pg` gem for Postgres or the `mysql2` gem for MySQL in your production bundle. Rails reads `DATABASE_URL` automatically and merges it with your `config/database.yml` file. However, an explicit `url:` in that file takes precedence, so if your configuration still contains a URL from a previous host, remove it so Rails uses Cloud's connection instead.

You should always use a managed database in production. Since your environment's [filesystem](/docs/environments#filesystem) is ephemeral, a SQLite file stored on an instance can't be shared across replicas and won't survive your next deployment.

### Solid Cache, Solid Queue, and Solid Cable

Rails applications may use separate production connections named `cache`, `queue`, and `cable`. Cloud detects these connections in your `config/database.yml` file, and when you attach your primary database, it creates a separate database for each one it finds. These databases live in the same database cluster as your primary connection.

For example, a Postgres application using all three Solid components might have the following configuration:

```yaml theme={null}
default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 3) %>

production:
  primary:
    <<: *default
    database: my_app_production
  cache:
    <<: *default
    database: my_app_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *default
    database: my_app_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *default
    database: my_app_production_cable
    migrations_paths: db/cable_migrate
```

You only need to keep the connections your application actually uses. If you are using MySQL, use the `mysql2` adapter and its gem instead. Cloud provides a connection URL for each connection, and Rails reads them automatically:

| Connection | Environment variable |
| - | - |
| `primary` | `DATABASE_URL` |
| `cache` | `CACHE_DATABASE_URL` |
| `queue` | `QUEUE_DATABASE_URL` |
| `cable` | `CABLE_DATABASE_URL` |

To have Cloud provision these databases, keep the conventional connection names and leave out any `url:` entries. It skips connections that have an explicit URL or `replica: true`, and you will need to configure connections with any other name yourself. Since Cloud reads the structure of your YAML file without evaluating its Ruby expressions, make sure your production connection definitions are written out in the file.

You may override the injected secondary URLs using your own environment variables or linked secrets, but those external connections are then yours to manage. When you replicate an environment, Cloud copies or creates the attached secondary databases based on the database replication option you choose. If you have set custom URLs, review them before deploying a replica or preview environment so it doesn't accidentally use your production data.

<Note>
  Changing `config/database.yml` on its own doesn't update an existing environment's database attachments, so be sure to declare your connections before Cloud scans your repository and you attach the database. Detaching a database removes the environment's secondary connections, but the databases themselves are kept.
</Note>

Cloud waits for your attached databases to become available before deploying. Keep in mind that creating these connections doesn't install the Solid gems or start a queue worker for you. You will still need to configure each Solid component to use its corresponding Rails connection and include its migration or schema files. The [Rails multiple database guide](https://guides.rubyonrails.org/active_record_multiple_databases.html) walks through the application configuration.

### Prepare the databases

Database preparation belongs in your environment's [deploy commands](/docs/environments#deploy-commands):

```sh theme={null}
bin/rails db:prepare
```

Cloud runs this command just before your new deployment starts receiving traffic, and it cancels the rollout if the command fails. Rails prepares each of your configured databases and applies any pending migrations.

New Rails environments start with this command commented out. When you attach a database, Cloud enables it for you automatically, as long as you haven't customized your deploy command. If you have, simply add the preparation step yourself.

You should avoid running migrations from your start command. The start command runs every time an instance starts, including during deployments, autoscaling, and wake-ups after scaling to zero. Since your previous deployment keeps running while the new one rolls out, you should also write your migrations so they remain compatible with both.

## Build and serve assets

When Cloud detects Propshaft or Sprockets in a Rails application that isn't configured as API-only, it pre-fills the following build command:

```sh theme={null}
SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
```

If Cloud also detects a JavaScript package manager, it adds a step to install your JavaScript dependencies. Ruby runtimes include Node.js, npm, and Yarn. Be sure to keep the dependencies your asset build needs, including any development dependencies used by your JavaScript build tools.

If you use a custom asset pipeline, review the pre-filled command and adjust it as needed. API-only Rails applications don't receive this default asset build. Cloud's Nginx server serves files from your application's `public` directory, including the compiled assets in `public/assets`.

The `SECRET_KEY_BASE_DUMMY=1` setting only applies to asset compilation. Your real `SECRET_KEY_BASE` is still used at runtime, so don't add the dummy setting as an environment variable. Also, keep in mind that initializers that access your credentials or database may still require those resources while your assets compile.

## Caches and background jobs

When you attach a [Laravel Valkey](/docs/resources/caches/valkey) or [Redis](/docs/resources/caches/redis) cache, Cloud provides a `REDIS_URL` variable. You should configure your cache library or Rails cache store to use this URL, and make sure it supports TLS when the URL uses the `rediss://` scheme.

To use Rails' Redis cache store, include the `redis` gem and add the following to your `config/environments/production.rb` file:

```ruby theme={null}
config.cache_store = :redis_cache_store, { url: ENV.fetch("REDIS_URL") }
```

If your application uses Solid Cache, you may keep your existing database cache configuration instead. Either way, choose the backing store your application is configured to use.

You may run your background jobs as [custom background processes](/docs/workers#custom-background-processes) on your App cluster or a worker cluster. Set the command for the job backend you are using:

| Backend | Example command |
| - | - |
| Solid Queue | `bundle exec bin/jobs` |
| Sidekiq | `bundle exec sidekiq` |
| GoodJob | `bundle exec good_job start` |

Be sure to keep the required gems and binstubs in your production bundle. Cloud suggests Sidekiq and GoodJob commands based on your lockfile, along with any processes from your Procfile. If you are using Solid Queue with `bin/jobs`, add the process yourself. You should run your workers as separate background processes rather than enabling `SOLID_QUEUE_IN_PUMA` on your web server.

Each background process runs on every replica of its cluster, so account for your total worker concurrency and database connections when choosing process counts and replica limits. If your workers need to process jobs or recurring tasks while your App cluster [scales to zero](/docs/workers#workers-and-scale-to-zero), keep their worker cluster awake.

Solid Queue can run recurring tasks using its own scheduler and `config/recurring.yml` file. To use it, configure your recurring tasks in your application and run the jobs process. Cloud's scheduler controls for Laravel and Symfony don't configure Rails recurring tasks.

## Store files in object storage

For user uploads, you should use [object storage](/docs/resources/object-storage). To get started, attach a private bucket as your environment's default disk. Cloud provides the bucket's AWS connection variables, but you will need to select and configure an Active Storage service in Rails yourself.

First, add the `aws-sdk-s3` gem to your production `Gemfile`, run `bundle install` locally, and commit the updated lockfile. Then, add the following service to your `config/storage.yml` file:

```yaml theme={null}
cloud:
  service: S3
  access_key_id: <%= ENV.fetch("AWS_ACCESS_KEY_ID") %>
  secret_access_key: <%= ENV.fetch("AWS_SECRET_ACCESS_KEY") %>
  region: <%= ENV.fetch("AWS_REGION", "auto") %>
  bucket: <%= ENV.fetch("AWS_BUCKET") %>
  endpoint: <%= ENV.fetch("AWS_ENDPOINT_URL") %>
  force_path_style: false
```

Finally, select the service in your `config/environments/production.rb` file:

```ruby theme={null}
config.active_storage.service = :cloud
```

With this configuration, your uploads live outside of your instance's filesystem, and Rails uses signed URLs to provide access to them. Be sure to keep the service private and leave out any per-object ACL options. Cloud's object storage manages visibility at the bucket level, so it rejects the ACL requests Active Storage sends when a service uses `public: true`.

The Ruby runtime includes libvips for image variants. If your application transforms images, include the `image_processing` gem as well. The [Active Storage guide](https://guides.rubyonrails.org/active_storage_overview.html) covers direct uploads and variant configuration in more detail.

## Configure Puma concurrency

You should configure Puma's process and thread counts based on your instance's CPU and memory. Unlike Python applications, Cloud doesn't calculate `WEB_CONCURRENCY` for Ruby applications. You may set it yourself, or start with Puma's single-process mode and increase concurrency once you have a feel for your application's memory usage.

For example, you might add the following settings to your existing `config/puma.rb` file:

```ruby theme={null}
thread_count = Integer(ENV.fetch("RAILS_MAX_THREADS", "3"))
threads thread_count, thread_count
workers Integer(ENV.fetch("WEB_CONCURRENCY", "0"))

force_shutdown_after 20
worker_shutdown_timeout 25
```

Setting `WEB_CONCURRENCY=0` runs a single server process without clustered workers. Keep in mind that each clustered worker has its own thread pool and its own database connection pools, so set the pool for each database large enough to accommodate every thread that uses it, including any background or asynchronous work.

## Shut down gracefully

When an instance stops, whether during a deployment or while scaling down, Cloud sends a `SIGTERM` signal to your application's process. Puma handles this signal gracefully by letting active requests finish before exiting.

You should keep Puma's shutdown limits, such as the `force_shutdown_after` and `worker_shutdown_timeout` settings in the previous example, shorter than your environment's **graceful shutdown timeout**. This leaves time for the proxy and the rest of the instance to stop cleanly. If your requests or jobs need more time to finish, increase the environment's timeout as well. Puma's [configuration documentation](https://puma.io/puma/Puma/DSL.html) describes the available shutdown and concurrency options.

## Running behind the proxy

Cloud handles TLS for you before forwarding requests to your application, so Puma only needs to speak plain HTTP. Along the way, its proxy passes along the original request scheme in the `X-Forwarded-Proto` header, which allows Rails to recognize HTTPS requests. You should keep your production HTTPS configuration, such as `config.force_ssl = true`, in place.

If you restrict hosts using `config.hosts`, be sure to allow your Cloud domain and any custom domains. You should also configure the hostnames used in URLs generated outside of a request, such as links in your mailers. Cloud provides an `APP_URL` variable, but Rails doesn't apply it to these settings automatically.

## Logging

Cloud captures everything your application writes to stdout and stderr and displays it in the [Logs](/docs/logs) dashboard. Recent Rails applications already log to stdout. If you are deploying an older application that only writes to a log file, configure its logger in your `config/environments/production.rb` file:

```ruby theme={null}
config.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
```

Keep in mind that the injected `RAILS_LOG_TO_STDOUT` variable only has an effect when your application's configuration reads it. The [Rails configuration guide](https://guides.rubyonrails.org/configuring.html) is a helpful reference when adapting an older application.

## Other Ruby and Rack applications

For Ruby applications that don't use Rails, Cloud uses the `web` command from your Procfile if you have one. Otherwise, it uses the Ruby or Rack entrypoint you selected. A Ruby file such as `app.rb` runs with `bundle exec ruby app.rb`, so your program needs to start its own server, read `PORT`, and accept IPv6 connections.

For a Rack entrypoint such as `config.ru`, Cloud pre-fills the following start command:

```sh theme={null}
bundle exec rack-start.rb config.ru
```

Cloud's launcher uses Puma when it is available in your production bundle, or a compatible `rackup` executable otherwise, and it binds the server to `::` on `PORT` for you. Be sure to add Puma, or Rackup with a compatible server, to your production dependencies.

Generic Ruby applications receive `RACK_ENV=production`, `BUNDLE_WITHOUT=development:test`, and the connection variables for any attached resources. Their `DATABASE_URL` uses the `mysql://` or `postgresql://` scheme, so you may need to adapt it for the database library you use. The Rails-specific asset builds, secrets, and database preparation described in this guide don't apply to generic Ruby applications.
