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

# Build a Rails app on floo

> End-to-end guide. Deploy a Rails app, add Postgres, add per-user auth, and ship a custom domain, all from a real Rails project.

From a Rails 7.1+ project on GitHub (or a fresh `rails new`) to a live app at `https://<app>.getfloo.app`, with managed Postgres, signed-in users, and your own domain.

New to floo? Start with the [Quickstart](/docs/introduction).

## 1. Add a Dockerfile

This walkthrough uses Postgres from the first deploy. Start a new app with `rails new myapp --database=postgresql`, or add the `pg` gem and run `bundle install` in your existing app before continuing.

`rails new` on Rails 7.1+ writes a production-ready `Dockerfile`. If yours has none, generate one with the `dockerfile-rails` gem:

```bash theme={null}
bundle add dockerfile-rails --optimistic --group development
bin/rails generate dockerfile
```

Or write a minimal one:

```dockerfile Dockerfile theme={null}
FROM ruby:3.3-slim

ENV RAILS_ENV=production
ENV RAILS_LOG_TO_STDOUT=1
ENV RAILS_SERVE_STATIC_FILES=1

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
      build-essential libpq-dev libyaml-dev && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY Gemfile Gemfile.lock ./
RUN bundle config set --local without 'development test' && \
    bundle install --jobs 4

COPY . .
RUN SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile

CMD ["sh", "-c", "bin/rails server -b 0.0.0.0 -p $PORT"]
```

`RAILS_ENV=production` is set before `assets:precompile` so the production asset pipeline runs, and `SECRET_KEY_BASE_DUMMY=1` lets Rails 7.1+ precompile without the real secret, which is not available at build time.

<Tip>
  Bind to `0.0.0.0` on `$PORT`, not `localhost` on a fixed port. floo sets `PORT` at runtime and routes only to processes bound to all interfaces.
</Tip>

## 2. Initialize the floo config

Install the CLI and sign in: `curl -fsSL https://getfloo.com/install.sh | bash`, then `floo auth login`. An agent can run the same journey over the HTTP API instead, starting from [getfloo.com/agents.md](https://getfloo.com/agents.md).

From the repo root:

```bash theme={null}
floo init my-rails-app
```

This writes `floo.app.toml`. For a single-service Rails app:

```toml floo.app.toml theme={null}
[app]
name = "my-rails-app"

[services.web]
type = "web"
path = "."
port = 3000
ingress = "public"
dev_command = "bin/rails server -p 3000"
migrate_command = "bin/rails db:migrate"

[managed.default]
type = "postgres"
```

`migrate_command` runs as a one-off job with the new image before traffic
moves to it. A failed migration stops the deploy. Use
[expand-then-contract migrations](/docs/guides/postgres#schema-portability-across-dev-and-prod)
to keep the schema compatible with both old and new code.

## 3. Connect the repo and deploy

floo builds from GitHub (public or private repositories), not local files, so push first. `floo apps github connect` needs the floo GitHub App on the account that owns the repository, and opens a browser to install it if it is missing.

```bash theme={null}
floo preflight
git add . && git commit -m "feat: floo config + Dockerfile"
git push origin main
floo apps github connect owner/my-rails-app --no-deploy
bin/rails secret | floo env set SECRET_KEY_BASE --stdin --secret --app my-rails-app
git commit --allow-empty -m "chore: start the first deploy" && git push origin main
```

`github connect`:

1. Creates the floo app if it doesn't exist.
2. Wires the GitHub repo as the source.
3. With `--no-deploy`, waits for the next push. Set the runtime secret before that push; the asset-build dummy is not a runtime secret.

Watch the build:

```bash theme={null}
floo deploys watch --app my-rails-app
```

When it's done:

```bash theme={null}
floo apps show my-rails-app
```

<Check>
  **Your Rails app is live** at `https://my-rails-app-dev.getfloo.app`.

  Every `git push origin main` ships to dev. `floo releases promote --app my-rails-app` publishes to `https://my-rails-app.getfloo.app`. The first promote creates prod; set a separate `SECRET_KEY_BASE` with `--env prod` and promote again before expecting a successful production boot.
</Check>

## 4. Add a Postgres database

The declaration in step 2 provisions Postgres on the first deploy and injects `DATABASE_URL` plus the standard `PG*` component variables. Use Postgres for persistent application data; a default SQLite database in the container filesystem is ephemeral.

Rails reads `DATABASE_URL` automatically. Confirm `config/database.yml` has the production block:

```yaml config/database.yml theme={null}
production:
  primary:
    url: <%= ENV["DATABASE_URL"] %>
```

The next deploy runs `bin/rails db:migrate` against the database. Dev and prod each get a separate database and credentials, with no shared state and no cross-environment leaks.

`DATABASE_URL` is a normal PostgreSQL URI that ActiveRecord parses directly, with no custom socket parsing in `config/database.yml`.

`floo preflight` also warns if a local env file still contains the old socket-style `DATABASE_URL` shape (`postgresql://user:pass@/db?host=/cloudsql/...`), which Ruby's URI parser rejects before Rails can boot.

Need embeddings? Add `CREATE EXTENSION IF NOT EXISTS vector;` to your first migration, then declare a bare `vector` column (`t.vector :embedding, limit: 1536`, or the `neighbor` gem). See [Postgres → Vector search](/docs/guides/postgres#vector-search-with-pgvector).

## 5. Add Redis (cache, jobs, Action Cable)

Managed Redis is cache-only: treat its contents as disposable. floo makes no backup, restore, or durability guarantee for it; keep authoritative data in Postgres.

```toml floo.app.toml theme={null}
[managed.cache]
type = "redis"
```

Run preflight, commit, and push the declaration before using the generated credentials.

This injects `REDIS_URL_CACHE` (a `rediss://` URL). One instance can back Rails cache, Sidekiq, and Action Cable at once:

```ruby config/environments/production.rb theme={null}
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL_CACHE"] }
```

```ruby config/initializers/sidekiq.rb theme={null}
Sidekiq.configure_server { |c| c.redis = { url: ENV["REDIS_URL_CACHE"] } }
Sidekiq.configure_client { |c| c.redis = { url: ENV["REDIS_URL_CACHE"] } }
```

```yaml config/cable.yml theme={null}
production:
  adapter: redis
  url: <%= ENV["REDIS_URL_CACHE"] %>
```

Rails namespaces cache keys, Sidekiq uses its own prefixes, and Action Cable uses pub/sub channels, so they coexist on one instance without collision.

Keep authoritative records of queued work in Postgres so your app can regenerate
Sidekiq jobs if the Redis queue is lost.

**Enqueue here, run there.** Run Sidekiq as its own floo service that attaches the same Redis. Both `web` and `worker` declare `redis:cache`, so floo injects the same `REDIS_URL_CACHE` into both. A worker takes no deployed HTTP traffic, and its `ingress` defaults to internal. The local `floo dev` runner requires a distinct port in the config even for a worker. Because the worker shares the `web` build, it must declare its own `command`:

```toml floo.app.toml theme={null}
[services.web.env]
managed = ["postgres", "redis:cache"]

[services.worker]
type = "worker"
path = "."
command = "bundle exec sidekiq"
dev_command = "bundle exec sidekiq"
port = 3001

[services.worker.env]
managed = ["postgres", "redis:cache"]
```

Want cache and queue on physically separate instances? Declare named ones (`[managed.cache]`, `[managed.queue]`) and read `REDIS_URL_CACHE` / `REDIS_URL_QUEUE`. See [Redis](/docs/guides/redis).

## 6. Add file storage (Active Storage)

```toml floo.app.toml theme={null}
[managed.uploads]
type = "storage"
```

Run preflight, commit, and push the declaration before using the bucket.

This injects `STORAGE_BUCKET_UPLOADS` and the `STORAGE_S3_*_UPLOADS` keys. Add it to the web service's existing attachment list: `managed = ["postgres", "redis:cache", "storage:uploads"]`. Add `gem "aws-sdk-s3", require: false` and configure Active Storage:

```yaml config/storage.yml theme={null}
amazon:
  service: S3
  bucket: <%= ENV["STORAGE_BUCKET_UPLOADS"] %>
  endpoint: <%= ENV["STORAGE_S3_ENDPOINT_UPLOADS"] %>
  region: <%= ENV["STORAGE_S3_REGION_UPLOADS"] %>
  access_key_id: <%= ENV["STORAGE_S3_ACCESS_KEY_ID_UPLOADS"] %>
  secret_access_key: <%= ENV["STORAGE_S3_SECRET_ACCESS_KEY_UPLOADS"] %>
  request_checksum_calculation: when_required
  response_checksum_validation: when_required
```

```ruby config/environments/production.rb theme={null}
config.active_storage.service = :amazon
```

Active Storage's `:google` service remains an alternative with Application Default Credentials:

```yaml config/storage.yml theme={null}
google:
  service: GCS
  bucket: <%= ENV["STORAGE_BUCKET_UPLOADS"] %>
```

For `:google`, use `config.active_storage.service = :google` and `config.active_storage.resolve_model_to_route = :rails_storage_proxy`; upload through a Rails controller. Services created before v2026.09.27.1 have no S3 keys; use `:google`, or migrate and verify data in a new named service before removing the old one. New buckets allow browser POST uploads through floo's signed policy, but Rails direct uploads use PUT, which the default CORS policy does not allow. See [Cloud storage](/docs/guides/cloud-storage).

## 7. Add per-user auth

Set `access_mode = "accounts"` and floo handles sign-in:

```toml floo.app.toml theme={null}
[app]
name = "my-rails-app"
access_mode = "accounts"
```

From the next deploy onward, floo's gateway:

* Redirects unauthenticated requests to a hosted login page.
* Validates the session cookie on every request.
* Injects identity headers into every request that reaches your Rails app.

Controllers read the headers, with no auth code, callback handlers, or session storage:

```ruby app/controllers/application_controller.rb theme={null}
class ApplicationController < ActionController::Base
  before_action :load_floo_user

  private

  def load_floo_user
    @current_user_email = request.headers["X-Floo-User-Email"]
    @current_user_id    = request.headers["X-Floo-User-Id"]
    @current_user_name  = request.headers["X-Floo-User-Name"]
  end
end
```

Locally, run `floo dev --fixture-user` (see [section 9](#9-local-development-with-dev-credentials)). It injects the same `X-Floo-User-*` headers floo's gateway adds in production, so the controller above works locally with no conditional code. Access modes and identity headers in full: [Auth](/docs/guides/app-auth).

## 8. Add a custom domain

Declare the domain in `floo.app.toml`:

```toml theme={null}
[domains."app.example.com"]
service = "web"
```

Commit and push, wait for that commit's dev deploy to go live, then release it
to prod and read the DNS records:

```bash theme={null}
floo releases promote --app my-rails-app
floo domains show app.example.com --app my-rails-app
```

Publish the traffic and certificate CNAME records that `show` prints, then watch
activation:

```bash theme={null}
floo domains watch app.example.com --app my-rails-app
```

Rails reads `request.host` for request-scoped URLs. Mailers have no request context: set `config.action_mailer.default_url_options = { host: "app.example.com", protocol: "https" }` in the production environment config. DNS setup and troubleshooting: [Custom domains](/docs/guides/custom-domains).

## 9. Local development with dev credentials

After the first deploy, iterate locally against dev without redeploying.

### Local dev server

```bash theme={null}
floo dev --app my-rails-app
```

Runs `dev_command` (the `bin/rails server` line in `floo.app.toml`) locally with `DATABASE_URL` and readable env vars from the app's dev environment: a real connection to your dev Postgres from your laptop, with no credentials in your shell history. Write-only secrets are withheld; supply separate local values when needed. Prod credentials never reach a local session.

To test signed-in flows without a fixture-user shim in your Rails code, add `--fixture-user`:

```bash theme={null}
floo dev --app my-rails-app --fixture-user you@example.com
```

`floo dev` then puts a proxy in front of each accounts-mode service that injects the same `X-Floo-User-*` headers floo's gateway adds in production. The output table shows the raw service URL and the auth-proxied URL; use the auth-proxied one for any path that reads identity headers.

### One-shot commands: rake tasks, console, db:seed

`floo run` executes one command, with no server, using the same dev-environment env vars `floo dev` injects. Pass the command after `--`:

```bash theme={null}
# Run a rake task against your dev database
floo run --service web -- bundle exec rake my_task

# Seed the dev database
floo run --service web -- bin/rails db:seed

# Open a Rails console connected to dev Postgres
floo run --service web -- bin/rails console

# Run a migration manually (outside the deploy path)
floo run --service web -- bin/rails db:migrate
```

`floo run` inherits stdin, stdout, and stderr, so interactive commands like `bin/rails console` and `byebug` work as they do locally. The difference is that the command sees floo-injected env vars instead of your local `.env`.

<Tip>
  Deploys run migrations through `migrate_command` in `floo.app.toml`. Use `floo run --service web -- bin/rails db:migrate` only for ad-hoc migration work outside the deploy path.
</Tip>

## Common gotchas

* **`/healthz` is reserved.** floo's edge intercepts that exact path. Use `/health` or `/livez` for health checks.
* **`bind: 0.0.0.0`, not `localhost`.** Rails binds to localhost by default in some configs, which floo cannot reach.
* **Asset compilation.** The Dockerfile above sets `RAILS_SERVE_STATIC_FILES=1` so Rails serves precompiled assets directly.
* **Force-SSL.** Rails 7+ defaults `config.force_ssl = true` in production. floo's edge terminates TLS and forwards `X-Forwarded-Proto: https`, so this works without extra config.
* **Schema dumps.** See [Postgres → Schema portability across dev and prod](/docs/guides/postgres#schema-portability-across-dev-and-prod).

## What's next

<CardGroup cols={2}>
  <Card title="Auth, full reference" href="/docs/guides/app-auth">
    Identity headers, access policies, and access modes in detail.
  </Card>

  <Card title="Managed services" href="/docs/guides/managed-services#usage-metering">
    Postgres, Redis, Storage: what they cost and how isolation works.
  </Card>

  <Card title="Custom domains" href="/docs/guides/custom-domains">
    DNS, verification, multi-service routing.
  </Card>

  <Card title="Environments" href="/docs/guides/environments">
    Dev vs prod, promotion, env overrides.
  </Card>
</CardGroup>


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