Skip to main content
From a Next.js 14+ App Router project on GitHub (or a fresh npx create-next-app@latest) 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

Configure standalone output, which makes a much smaller container:
next.config.js
Then a multi-stage Dockerfile that uses it:
Dockerfile
floo sets PORT at runtime and the standalone server reads it, so do not hardcode a port. Set HOSTNAME=0.0.0.0: Next.js standalone defaults to localhost, which floo cannot reach.
Add a .dockerignore next to this Dockerfile with at least node_modules, .git, and .env. Without it, COPY . . copies a committed node_modules (or your local one, in a local docker build) over the clean install from the deps stage. See Build context.

2. Build-time env vars (NEXT_PUBLIC_*)

Any NEXT_PUBLIC_* variable your code references is baked into the JS bundle at build time, not read at runtime. floo passes every env var whose name starts with NEXT_PUBLIC_, VITE_, or REACT_APP_ to the image build as a Docker build arg, and injects it at runtime too. Two things have to line up. First, once step 4 has created the app, set the value as an ordinary env var. The name prefix is what makes it a build arg; no extra flag is needed.
Second, thread it through the build stage of the Dockerfile. ARG makes Docker accept the value; ENV puts it in process.env for next build.
Dockerfile
Step 4’s first deploy builds before the var exists. Once both are in place, rebuild:
Skipping either step is the most common Next.js failure on floo: the app builds, deploys, and 404s in the browser because the bundle has undefined baked in. The build step records a diagnostic when a build-time var is passed but not threaded into the stage that runs next build. Env vars are per environment (--env dev is the default). After the first release creates prod, set any differing build-time values there and publish another release to rebuild. See Environments for image reuse and rebuild rules:
For calls to your own app, relative paths (/api/...) avoid the question entirely. See Container contract and Environment variables.

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.
For a single-service Next.js app:
floo.app.toml
If your image includes a migration tool and its migration files, set migrate_command under [services.web]. 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. Before the first floo apps github connect, a human installs the floo GitHub App on the account that owns the repository and links it to your floo org; see Quickstart steps 2 and 3. If either step is missing, connect opens a setup link and waits.
When the deploy is green:
Your Next.js app is live at https://my-nextjs-app-dev.getfloo.app.Every git push origin main ships to dev. gh release create v1 --repo <owner>/<repo> publishes to https://my-nextjs-app.getfloo.app. See Environments.

5. Add a Postgres database

floo.app.toml
DATABASE_URL is injected into the runtime. Configure your database client to read it. For Prisma, follow the configuration for your installed Prisma version. Next.js standalone output does not automatically include the Prisma CLI, schema, or migration files. Include those in the runtime image and generate the client during the build before setting migrate_command = "npx prisma migrate deploy". The command must run from the packaged dependencies without downloading tools at deploy time.

Optional: add file storage

Add [managed.uploads] with type = "storage" and attach it to the web service. Once a service lists managed, it receives only the services listed there, so keep postgres in the list or DATABASE_URL disappears:
floo.app.toml
Then deploy. Configure the client in a server route; see Cloud storage for spending restrictions and file operations. Keep keys server-side.

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 Next.js app.
Read the headers in a Server Component or Route Handler:
app/dashboard/page.tsx
Locally, run floo dev --fixture-user (see section 8) or hit your dev server with curl -H 'X-Floo-User-Email: you@example.com'. Access modes and identity headers in full: App sign-in.

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:
Next.js reads Host and X-Forwarded-Host from the request, 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.

Faster pushes

A push that changes only code reuses every layer above COPY . ., so its build time is mostly next build, and type checking is usually the longest part of it.
  • Run npm run build (or next build) as its own RUN line, as in the Dockerfile above. floo then keeps .next/cache between pushes, so Next.js recompiles only what changed. A cd, a comment, a heredoc, or the exec form (RUN ["npm", "run", "build"]) on that line turns this off.
  • Keep "incremental": true in tsconfig.json. create-next-app sets it. With it, type checking reuses the previous push’s results from .next/cache.
  • Copy package.json and the lockfile before the source, as in the deps stage above. npm ci then reruns only when the lockfile changes. A push that changes dependencies also produces a larger new image layer, so it takes longer to build and to start.
  • In the final stage, copy anything that rarely changes before .next/standalone, such as a full node_modules for a migration tool. A layer after a changed one is rebuilt and uploaded on every push. See Layer order.
TypeScript 7 type-checks 8–12x faster than TypeScript 6 on full builds (announcement). next build runs your project’s own tsc by default (docs), so installing typescript@^7 is enough for floo builds to use it. It turns on strict by default and ships no JavaScript API yet, so tools such as typescript-eslint need @typescript/typescript6 alongside it.

Build files from the edge cache

floo serves /_next/static/* from its edge cache, close to the visitor. It caches what your app marks cacheable: Next.js sends hashed build files with Cache-Control: public, max-age=31536000, immutable, so after the first request they load from the cache and never reach your app. There is nothing to configure.
  • Only public apps are cached. An app behind sign-in or an edge policy gets private on every response, and the cache stores nothing for it. See Caching signed-in responses.
  • When an app stops being public, gains an edge policy, or loses a host, floo clears that host’s cached files.
  • Requests to /_next/static/* bill as edge cache requests, and files served from the cache bill as edge cache bandwidth. Rates are on getfloo.com/pricing. Cache hits are not counted in floo analytics.

Common gotchas

  • /healthz is reserved. floo’s edge intercepts that exact path. Use /health or /livez.
  • HOSTNAME=0.0.0.0. Next.js standalone defaults to localhost, which floo cannot reach.
  • NEXT_PUBLIC_* build args. Set them with floo env set and thread them through the build stage as ARG plus ENV, or the bundle has undefined baked in.
  • output: "standalone" in next.config.js. The Dockerfile above assumes standalone output. Without it, the build artifact is much larger and the COPY --from=build /app/.next/standalone line fails.
  • A .dockerignore listing node_modules. COPY . . runs after the clean install is copied in, so without one your local node_modules overwrites it.

Next steps

App sign-in

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

Multi-service routing

Deploy Next.js alongside a FastAPI or Express backend with shared origin.

Environment variables

Build-time vs runtime env vars, secrets, and per-service scoping.

Custom domains

DNS, verification, multi-service routing.