> ## 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.

# Cache a Rails application

> Cache Rails data with Solid Cache in the application's PostgreSQL database, or with a Sevalla Redis or Valkey database and redis_cache_store, and prove a cache hit.

Rails keeps computed values, page fragments and other cached data in its cache store, which every instance of every process shares. A new Rails 8 application caches with Solid Cache, which stores entries in a database table, so on Sevalla the cache lives in the application's PostgreSQL database with no other service. For lower latency or a cache separate from the database, use a Sevalla Redis or Valkey database instead.

## At a glance

What this guide adds to the application from [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md#at-a-glance):

| | |
| - | - |
| Services | Solid Cache: none, the cache is a table in the deploy guide's PostgreSQL database. Redis: a Redis or Valkey database over an internal connection |
| Size | **DB1** for the Redis database |

### Environment variables

Solid Cache needs no variables. With Redis or Valkey:

| Variable | Required | Available during | Secret | Value |
| - | - | - | - | - |
| `REDIS_URL` | Yes | Runtime | Yes | Added by the internal connection: `redis://:<password>@<host>:<port>/0`, or `valkey://` for Valkey |
| `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` | No | Runtime | `REDIS_PASSWORD` | Added by the internal connection. The cache store reads only `REDIS_URL`. |

The application's other variables are in [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md#environment-variables).

## Before you begin

* A Rails application deployed on Sevalla, as in [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md). The deploy guide creates Solid Cache's `solid_cache_entries` table in the application's database.

## Cache a value

`Rails.cache.fetch` returns the cached value for a key, or runs the block, stores its result and returns it. Add a page that caches the time it was generated for 2 minutes:

```ruby app/controllers/pages_controller.rb theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
class PagesController < ApplicationController
  def cached
    generated_at = Rails.cache.fetch("pages/cached", expires_in: 2.minutes) do
      Time.current.strftime("%H:%M:%S")
    end
    render plain: "Generated at #{generated_at}"
  end
end
```

In `config/routes.rb`, add the route under the `root` line:

```ruby config/routes.rb theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
  get "cached", to: "pages#cached"
```

The same store serves fragment caching in views (`<% cache product do %>`) and every other `Rails.cache` call. In development, `Rails.cache` keeps entries in memory, and fragment caching is off until you run `bin/rails dev:cache`.

## Cache with Solid Cache

Solid Cache needs no setup beyond the deploy guide: `config/environments/production.rb` already sets `config.cache_store = :solid_cache_store`, and `config/cache.yml` limits the cache to 256 MB (`max_size`), after which Solid Cache deletes the oldest entries. Commit and push the changes. If **Auto-deploy** is off, click **Deploy now** on the application's **Overview** tab.

When the deployment's status is **Deployed**, request the page twice, with your application's domain:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl https://storefront-x7k2m.sevalla.app/cached
curl https://storefront-x7k2m.sevalla.app/cached
```

Both responses show the same time, for example `Generated at 18:37:34`: the second one came from the cache. After 2 minutes, the next request shows a new time. To see the entry, open the database's **SQL studio** tab and look at the `solid_cache_entries` table. See [Database studio](https://docs.sevalla.com/databases/studio.md).

## Cache with Redis or Valkey

**1. Create the database**

In the sidebar, click **Databases**, then click **Create database**. Under **Database**, choose **Redis** or **Valkey**: the code in this guide works with both. Enter `storefront-redis` as the **Display name**, choose the same **Data center** as the application and a **Size**, then click **Create database**.

**2. Connect it to the application**

In the sidebar, click **Applications**, then click the application. Click the **Networking** tab. Under **Connected services**, click **Add internal connection**. Choose `storefront-redis` as the **Service**, turn on **Add environment variables to the application**, and click **Add internal connection**. Sevalla adds `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` and `REDIS_URL`. See [Connect an application](https://docs.sevalla.com/databases/connect.md#connect-an-application).

![The Add internal connection sheet with the Service field and the Add environment variables to the application switch](https://assets.sevalla.com/screenshots/light/app-networking-internal-connection-add.png)

Add the `redis` gem, which Rails' Redis cache store uses:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
bundle add redis
```

In `config/environments/production.rb`, replace the `config.cache_store = :solid_cache_store` line:

```ruby config/environments/production.rb theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
  # A Valkey database's URL starts with valkey://, which the redis gem doesn't accept.
  config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"]&.sub(/\Avalkey:/, "redis:") }
```

Commit and push the changes, `Gemfile.lock` included. If **Auto-deploy** is off, click **Deploy now** on the application's **Overview** tab. The internal connection's variables apply with this deployment.

Request `/cached` twice, as in [Cache with Solid Cache](#cache-with-solid-cache): both responses show the same time. Open the Redis database and click the **Redis studio** tab: the cache's key is `pages/cached`. The `solid_cache_entries` table stays in the database, unused; Solid Queue and Solid Cable keep their own tables.

### Check from a terminal

These checks need no dashboard. `API`, `H`, `APP_ID` and `WEB_ID` are set as in [Deploy with the API](#deploy-with-the-api).

1. The cache store Rails uses, with [Run a command in a process](https://api-docs.sevalla.com/v3/applications/run-a-command-in-a-process) (`execProcessCommandV3`) in the web process. The command runs without a shell, and the key needs write access to the application:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s -X POST "$API/applications/$APP_ID/processes/$WEB_ID/exec" -H "$H" -H "Content-Type: application/json" \
     -d '{"command": ["bin/rails", "runner", "puts Rails.cache.class"], "timeout": 60}' | jq -r .stdout
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   ActiveSupport::Cache::RedisCacheStore
   ```

   With Solid Cache, it prints `SolidCache::Store`.

2. A cache hit, with your domain:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s https://storefront-x7k2m.sevalla.app/cached; echo
   sleep 5
   curl -s https://storefront-x7k2m.sevalla.app/cached; echo
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   Generated at 05:42:51
   Generated at 05:42:51
   ```

## Deploy with a Dockerfile

The Dockerfile from [Deploy with a Dockerfile](https://docs.sevalla.com/guides/ruby/rails/deploy.md#deploy-with-a-dockerfile) needs no change: Solid Cache uses the `pg` gem the image already has, and the `redis` gem is plain Ruby, which `bundle install` installs from `Gemfile.lock`.

## Cache at the edge

Sevalla can also cache responses at Cloudflare's edge, in front of the application. See [CDN and edge caching](https://docs.sevalla.com/applications/caching.md). Rails already sends the headers that make this safe:

* Compiled assets in `/assets` get `Cache-Control: public, max-age=31556952` (1 year). Their file names include a digest of their content, so a new version gets a new URL.
* Pages get `Cache-Control: max-age=0, private, must-revalidate`, so the edge doesn't store them. Data you cache with `Rails.cache` stays on the server.

## Troubleshoot

| Symptom | Cause | Fix |
| - | - | - |
| `ArgumentError: Unknown URL scheme` with a `valkey://` URL | The `redis` gem accepts `redis://`, `rediss://` and `unix://` URLs only. | Keep the `.sub(/\Avalkey:/, "redis:")` in `config.cache_store`. |

## Deploy with the API

With Solid Cache, deploy the changes as in the deploy guide: [Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) and nothing else. For Redis, the same steps as [Cache with Redis or Valkey](#cache-with-redis-or-valkey), as calls to the [Sevalla API](https://docs.sevalla.com/developer-tools/api.md). `APP_ID`, `CLUSTER_ID` and `DB1_ID` are the application, its location and the **DB1** size from [Deploy with the API](https://docs.sevalla.com/guides/ruby/rails/deploy.md#deploy-with-the-api) in the deploy guide, with `API` and `H` set as there. Push the `redis` gem and the cache store change first: the deployment builds the branch as it is.

**1. Look up the Redis version**

[List database versions](https://api-docs.sevalla.com/v3/resources/list-database-versions) (`listDatabaseVersionsV3`) gives the newest Redis version in the location. For Valkey, select `valkey` here and in the next step:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REDIS_VERSION=$(curl -s "$API/resources/database-versions?cluster_id=$CLUSTER_ID" -H "$H" \
  | jq -r '.[] | select(.type == "redis") | .versions[-1]')
```

CLI: `sevalla resources db-versions --cluster $CLUSTER_ID --type redis`. MCP: `sevalla_api_read` with `listDatabaseVersionsV3`, or leave the version out of `create_database`, which takes the newest.

**2. Create the database**

[Create database](https://api-docs.sevalla.com/v3/databases/create-database) (`createDatabaseV3`). Redis has no user, and the API needs a `db_name` that it doesn't use for Redis. Poll [Get database](https://api-docs.sevalla.com/v3/databases/get-database) (`getDatabaseV3`) until it's `ready`; on `error`, delete the database and create it again:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REDIS_PASSWORD=$(openssl rand -hex 16)
REDIS_ID=$(curl -s -X POST "$API/databases" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg cluster "$CLUSTER_ID" --arg size "$DB1_ID" --arg version "$REDIS_VERSION" --arg password "$REDIS_PASSWORD" \
    '{display_name: "storefront-redis", type: "redis", version: $version, cluster_id: $cluster,
      resource_type_id: $size, db_name: "storefront", db_password: $password}')" \
  | jq -r .id)
for i in $(seq 1 60); do
  DB_STATUS=$(curl -s "$API/databases/$REDIS_ID" -H "$H" | jq -r '.status // empty')
  case $DB_STATUS in ready|error) break ;; esac
  sleep 10
done
echo "$DB_STATUS"
```

CLI: `sevalla databases create --name storefront-redis --type redis --db-version $REDIS_VERSION --cluster $CLUSTER_ID --resource-type $DB1_ID --db-name storefront --db-password $REDIS_PASSWORD`. MCP: `create_database` generates a password that it never returns. Pass `connect_to_application_id` and `env_var_name: "REDIS_URL"`: the tool connects the database and stores `redis://:<password>@<host>:<port>` in `REDIS_URL`, the only one the cache store reads, though without the `/0`. Then skip the next two steps.

**3. Connect it to the application**

[Create internal connection](https://api-docs.sevalla.com/v3/applications/create-internal-connection) (`createApplicationInternalConnectionV3`). It prints the connection's ID; if it prints nothing or `null`, run it again. The API doesn't add the connection's variables, so the next step does:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s -X POST "$API/applications/$APP_ID/internal-connections" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg db "$REDIS_ID" '{target_id: $db, target_type: "database"}')" | jq -r .id
```

CLI: `sevalla apps internal-connections create $APP_ID --target-id $REDIS_ID --target-type database`. MCP: `sevalla_api_create` with `createApplicationInternalConnectionV3` and the same body.

**4. Add the environment variables**

[Create environment variable](https://api-docs.sevalla.com/v3/applications/create-environment-variable) (`createEnvironmentVariableV3`), once per variable: the connection's variables in the dashboard's names and format, for runtime only. For a Valkey database, the dashboard writes `REDIS_URL` with `valkey://`, which `config.cache_store` turns into `redis://`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REDIS_HOST=$(curl -s "$API/databases/$REDIS_ID" -H "$H" | jq -r .internal_hostname)
REDIS_PORT=$(curl -s "$API/databases/$REDIS_ID" -H "$H" | jq -r .internal_port)
for pair in "REDIS_HOST=$REDIS_HOST" "REDIS_PORT=$REDIS_PORT" "REDIS_PASSWORD=$REDIS_PASSWORD" \
  "REDIS_URL=redis://:$REDIS_PASSWORD@$REDIS_HOST:$REDIS_PORT/0"; do
  curl -s -X POST "$API/applications/$APP_ID/env-vars" -H "$H" -H "Content-Type: application/json" \
    -d "$(jq -n --arg key "${pair%%=*}" --arg value "${pair#*=}" '{key: $key, value: $value, is_runtime: true, is_buildtime: false}')" \
    | jq -r .key
done
```

CLI: `sevalla apps env-vars create $APP_ID --key REDIS_URL --value <url>`, once per variable, for build and runtime. MCP: `set_environment_variables` with `runtime: true` and `buildtime: false`.

**5. Deploy**

[Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) (`triggerDeploymentV3`):

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
DEPLOYMENT_ID=$(curl -s -X POST "$API/applications/$APP_ID/deployments" -H "$H" -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
```

CLI: `sevalla apps deployments trigger $APP_ID`. MCP: `trigger_deployment`.

**6. Wait for the deployment**

Poll [Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) until the status is final: `success`, `failed`, `cancelled` or `skipped`. The loop keeps polling through a failed request and stops after 30 minutes:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for i in $(seq 1 120); do
  STATUS=$(curl -s "$API/applications/$APP_ID/deployments/$DEPLOYMENT_ID" -H "$H" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

CLI: `sevalla apps deployments get $DEPLOYMENT_ID --app-id $APP_ID`. MCP: `get_deployment`.

Then run the checks in [Check from a terminal](#check-from-a-terminal).


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