Skip to main content
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.

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:
Or write a minimal one:
Dockerfile
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.
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.

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. From the repo root:
This writes floo.app.toml. For a single-service Rails app:
floo.app.toml
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 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.
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:
When it’s done:
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.

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:
config/database.yml
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.

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.
floo.app.toml
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:
config/environments/production.rb
config/initializers/sidekiq.rb
config/cable.yml
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:
floo.app.toml
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.

6. Add file storage (Active Storage)

floo.app.toml
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:
config/storage.yml
config/environments/production.rb
Active Storage’s :google service remains an alternative with Application Default Credentials:
config/storage.yml
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.

7. Add per-user auth

Set access_mode = "accounts" and floo handles sign-in:
floo.app.toml
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:
app/controllers/application_controller.rb
Locally, run floo dev --fixture-user (see section 9). 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.

8. Add a custom domain

Declare the domain in floo.app.toml:
Commit and push, wait for that commit’s dev deploy to go live, then release it to prod and read the DNS records:
Publish the traffic and certificate CNAME records that show prints, then watch activation:
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.

9. Local development with dev credentials

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

Local dev server

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

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.

What’s next

Auth, full reference

Identity headers, access policies, and access modes in detail.

Managed services

Postgres, Redis, Storage: what they cost and how isolation works.

Custom domains

DNS, verification, multi-service routing.

Environments

Dev vs prod, promotion, env overrides.