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

This guide walks a Rails app from local code to a production URL with a database and per-user auth. Every step has runnable Rails code. By the end you have a working app at `https://<app>.on.getfloo.com` with a Postgres sibling service, signed-in users, and (optionally) your own domain.

If you've never deployed to floo before, read [Golden Path](/guides/golden-path) first for the minimal three-command flow. This page is the Rails-specific version.

## Before you start

You need:

* A Rails 7+ project (or a fresh `rails new`).
* The project pushed to a GitHub repository (public or private). floo pulls source from GitHub — it does not upload local files.
* The floo CLI installed and authenticated (`curl -fsSL https://getfloo.com/install.sh | bash` then `floo auth login`).
* The floo GitHub App installed on your repo's org. The CLI prompts you to install during `floo apps github connect`.

## 1. Add a Dockerfile

Rails 7.1+ ships with a production-ready `Dockerfile` from `rails new`. If yours doesn't, generate one:

```bash theme={null}
bin/rails generate dockerfile
```

Or write a minimal one:

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

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 bundle exec rails assets:precompile

ENV RAILS_ENV=production
ENV RAILS_LOG_TO_STDOUT=1
ENV RAILS_SERVE_STATIC_FILES=1
EXPOSE 3000

CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "3000"]
```

<Tip>
  Bind to `0.0.0.0`, not `localhost`. Cloud Run only routes traffic to processes bound to all interfaces.
</Tip>

## 2. Initialize the floo config

From the repo root:

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

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

```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"
```

`migrate_command` runs after every deploy (against the dev schema) and after every promote (against the prod schema). It's how Rails migrations stay in sync with deploys.

## 3. Connect the repo and deploy

```bash theme={null}
git add . && git commit -m "feat: floo config + Dockerfile"
git push origin main
floo apps github connect owner/my-rails-app
```

`github connect` does three things:

1. Creates the floo app if it doesn't exist.
2. Wires the GitHub repo as the source.
3. Triggers the first deploy.

Watch the build:

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

When it's done:

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

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

  Every `git push origin main` ships to dev. When you're ready, `floo releases promote --app my-rails-app` publishes to `https://my-rails-app.on.getfloo.com`.
</Check>

## 4. Local development and one-shot commands

Once your first deploy is up, you don't need to redeploy to iterate. Two commands cover the daily Rails workflow.

### Local dev server with prod-shaped env

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

Runs `dev_command` (the `bin/rails server` line in `floo.app.toml`) locally, with `DATABASE_URL` and other env vars sourced from your dev-environment floo app. You get a real Cloud SQL connection from your laptop without exporting credentials.

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

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

`floo dev` then starts a small 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 both the raw service URL and the auth-proxied URL — hit the auth-proxied one when testing dashboard/profile/etc. paths.

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

`floo run` executes a single command with the same managed-service env vars `floo dev` injects — no server, just the command. 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/stderr, so interactive commands like `bin/rails console` and `byebug` work the same as running them locally — the only difference is your shell sees the floo-injected env vars instead of whatever's in your local `.env`.

<Tip>
  Migrations run automatically on every deploy via `migrate_command` in `floo.app.toml`. Use `floo run -- bin/rails db:migrate` only for ad-hoc migration work outside the deploy path.
</Tip>

## 5. Add a Postgres database

```bash theme={null}
floo services add postgres --app my-rails-app
```

This provisions the database, injects `DATABASE_URL` plus standard `PG*` component vars into your app's runtime, and writes `.floo/services.lock`. Commit the lock file so PR reviewers see managed-service changes alongside code:

```bash theme={null}
git add .floo/services.lock && git commit -m "feat: add postgres"
git push origin main
```

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 your `migrate_command` against the new database before traffic shifts. Dev and prod each get isolated schemas and credentials — no shared state, no cross-environment leaks.

`DATABASE_URL` is a normal PostgreSQL URI, so Rails/ActiveRecord can parse it directly. You do not need custom Cloud SQL socket parsing in `config/database.yml`.

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

Need embeddings? Managed Postgres ships with **pgvector** enabled — declare a bare `vector` column (`t.vector :embedding, limit: 1536`, or the `neighbor` gem) with no `CREATE EXTENSION` step. See [Databases → Vector search](/guides/databases#vector-search-with-pgvector).

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

```bash theme={null}
floo services add redis --app my-rails-app
```

This injects `REDIS_URL` (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"] }
```

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

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

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

**Enqueue here, run there.** Run Sidekiq as its own floo service that attaches the same Redis. Both `web` and `worker` declare `managed = ["redis"]`, so floo injects the *identical* `REDIS_URL` into both — the web → worker handoff just works:

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

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

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

Want cache and queue on physically separate instances? Declare named ones (`[managed.cache]`, `[managed.queue]`) and read `REDIS_URL_CACHE` / `REDIS_URL_QUEUE`. See [Databases → Redis](/guides/databases#redis-shared-by-default-named-when-you-need-separation).

## 7. Add file storage (Active Storage)

```bash theme={null}
floo services add storage --app my-rails-app
```

This injects `STORAGE_BUCKET`. floo runs your container as a service account with read/write on that bucket, so Active Storage's `google` service authenticates automatically via Application Default Credentials — no key file, no `project:`:

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

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

Use **proxy mode** (`rails_storage_proxy`, above). floo grants the app read/write but not URL-signing, so Active Storage's default redirect-mode serving and browser direct uploads won't work — proxy mode streams blobs through your app and `has_one_attached` uploads go through your controllers, both server-side and signing-free. Don't use Active Storage `:amazon` / S3 SDKs; floo mints no S3 keys. Full contract: [Cloud Storage](/guides/cloud-storage).

## 8. Add per-user auth

floo manages user authentication for you. Set `access_mode = "accounts"` in `floo.app.toml`:

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

Push and deploy. From the next deploy onward, floo's gateway sits in front of your app and:

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

Your Rails controllers read the headers — no auth code, no callback handlers, no 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
```

For local development, run `floo dev --fixture-user` (see [section 4](#4-local-development-and-one-shot-commands)) — it injects the same `X-Floo-User-*` headers floo's gateway adds in production, so the controller above works locally without any conditional code.

For the full reference on access modes and identity headers, see [Add User Auth to Your App](/guides/app-auth).

## 9. Add a custom domain

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

The output shows the CNAME record to add at your DNS provider. Once it propagates, click "Verify DNS" in the dashboard or wait for the auto-poll.

If your app needs `request.host` to match the custom domain (for redirects, mailer URLs, etc.), Rails reads it from the `Host` header automatically — no extra config needed.

## Common gotchas

* **`/healthz` is reserved.** Cloud Run'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 — that won't accept Cloud Run traffic.
* **Asset compilation.** `RAILS_SERVE_STATIC_FILES=1` is set in the Dockerfile above so Rails serves precompiled assets directly. For high-traffic apps, push assets to floo's storage service or a CDN.
* **Force-SSL.** Rails 7+ defaults `config.force_ssl = true` in production. floo's edge handles TLS termination and forwards `X-Forwarded-Proto: https`, so this works correctly without extra config.
* **Schema dumps.** Each environment gets its own Postgres schema (`app_<id>_dev` / `_prod`); the `search_path` resolves your unqualified table names, so migrations need no changes. If you switch to `config.active_record.schema_format = :sql`, keep migrations schema-agnostic — a `structure.sql` dumped from one environment embeds that environment's schema name. The default `:ruby` format (`schema.rb`) avoids this.

## What's next

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

  <Card title="Managed Services" href="/guides/managed-services">
    Postgres, Redis, Storage — what they cost and how isolation works.
  </Card>

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

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