Skip to main content
From an Express 4/5 project on GitHub (or npm init -y && npm install express) 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

Dockerfile
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. The server.js below reads process.env.PORT.
Add a .dockerignore next to this Dockerfile with at least node_modules, .git, and .env. Without it, COPY . . copies your local node_modules over the clean install from the deps stage and ships your dev dependencies. See Build context.

2. Trust the proxy

floo’s edge sits in front of your app. Trust it so req.protocol, req.hostname, and req.ip reflect the original client request:
server.js

3. 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.
floo.app.toml
If your app has a migration script, set migrate_command = "node ./scripts/migrate.js" under [services.web] after adding the database. The script must be included in the image. It 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.

4. Connect the repo and deploy

floo builds from GitHub, 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.
When the deploy is green:
Your Express app is live at https://my-express-app-dev.getfloo.app.Every git push origin main ships to dev. floo releases promote --app my-express-app publishes to https://my-express-app.getfloo.app.

5. Add a Postgres database

floo.app.toml
DATABASE_URL is injected. With pg:
db.js
Or use Prisma (npx prisma init), which reads DATABASE_URL from the environment. If you set migrate_command, the next deploy runs it against the new database.

File storage

Add [managed.uploads] with type = "storage", attach storage:uploads, and deploy. Configure the client in your Express app; see Cloud storage for paygo restrictions and file operations.

6. 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 Express app.
Read the headers in any handler:
server.js
Locally, run floo dev --fixture-user (see section 8) or send the headers yourself. Access modes and identity headers in full: Auth.

7. 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:
With app.set("trust proxy", true), Express reads req.hostname from X-Forwarded-Host, so it needs no extra config. DNS setup and troubleshooting: Custom domains.

8. Local development with dev credentials

Runs dev_command locally with DATABASE_URL and readable env vars from the app’s dev environment: a real connection to your dev Postgres, 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, 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.

Common gotchas

  • /healthz is reserved. floo’s edge intercepts that exact path. Use /health or /livez.
  • Bind to 0.0.0.0. Pass the host explicitly, as in app.listen(port, "0.0.0.0", ...). floo cannot reach localhost.
  • app.set("trust proxy", true) is required. Without it, req.protocol is always http, secure cookies don’t get set, and req.ip is wrong.
  • A .dockerignore listing node_modules. COPY . . runs after the production install is copied in, so without one your local node_modules (dev dependencies included) overwrites it.
  • ESM vs CommonJS. The examples use ESM (import/export), which needs "type": "module" in package.json. For CommonJS, use require() and module.exports; the logic is identical.

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.

Multi-service routing

Deploy Express alongside a Next.js or React frontend with shared origin.

Custom domains

DNS, verification, multi-service routing.