floo’s primary config file is floo.app.toml. Services are declared inline under [services.<name>]. Managed-service credential attachments live under service env blocks. Managed Postgres, Redis, and Storage are provisioned either with floo services add or by declaring [managed.<name>] blocks — a deploy provisions anything declared-but-missing and never destroys. The legacy top-level [postgres], [redis], and [storage] sections are deprecated in favor of [managed.<name>].
floo.service.toml is an optional per-service config file used only when you want service configuration to live alongside each service’s code (the “delegated” layout).
floo init creates floo.app.toml with your service declared inline. This page is the full reference.
Config shapes
| Shape | Files | When to use |
|---|
| Single service | floo.app.toml | One service, with or without managed credential attachments |
| Inline multi-service | floo.app.toml | All services defined in one file |
| Delegated multi-service | floo.app.toml + per-service floo.service.toml | Service config lives alongside service code |
Single service
[app]
name = "my-app"
access_mode = "public"
[services.web]
type = "web"
port = 3000
ingress = "public"
env_file = ".env"
[resources]
cpu = "1"
memory = "512Mi"
max_instances = 10
Single service with managed services
Provision the durable resources with the CLI:
floo services add postgres --app crm
floo services add redis --app crm
floo services add storage --app crm
Then keep the app shape in config:
[app]
name = "crm"
[services.web]
type = "web"
path = "."
port = 3000
ingress = "public"
[services.web.env]
managed = ["postgres", "redis", "storage"]
Inline multi-service
[app]
name = "full-stack"
access_mode = "password"
[resources]
cpu = "1"
memory = "512Mi"
max_instances = 10
[services.web]
type = "web"
path = "./web"
port = 3000
ingress = "public"
[services.web.env]
managed = []
[services.api]
type = "api"
path = "./api"
port = 8080
env_file = "./api/.env"
[services.api.env]
managed = ["postgres", "redis"]
[services.worker]
type = "worker"
path = "./worker"
port = 8081
ingress = "internal"
cpu = "2"
memory = "2Gi"
[services.worker.env]
managed = ["postgres", "redis"]
Inline and delegated are mutually exclusive per service. If a service has type and port in floo.app.toml, do not also place a floo.service.toml in that service’s directory. The CLI rejects this during preflight.
Delegated multi-service
Root floo.app.toml:
[app]
name = "full-stack"
[services.web]
path = "./web"
[services.api]
path = "./api"
web/floo.service.toml:
[app]
name = "full-stack"
[service]
name = "web"
type = "web"
port = 3000
ingress = "public"
api/floo.service.toml:
[app]
name = "full-stack"
[service]
name = "api"
type = "api"
port = 8080
ingress = "public"
env_file = ".env"
Field reference
[app]
| Field | Type | Default | Description |
|---|
name | string | required | App name (DNS-safe) |
access_mode | string | "public" | public, password, accounts |
[services.<name>]
| Field | Type | Default | Description |
|---|
type | string | required | web, api, worker |
port | integer | required | Port the service listens on |
ingress | string | "public" | public (internet-facing) or internal (only reachable by other services in the same app) |
env_file | string | none | Relative path to env file synced on deploy |
path | string | . | Relative path to service directory (multi-service apps) |
dev_command | string | none | Shell command run by floo dev to start the service locally. Example: "npm run dev" |
migrate_command | string | none | Shell command run before floo dev starts the service (and after each deploy). Example: "alembic upgrade head" |
domain | string | none | Custom domain for this service. Example: "api.example.com" |
[services.<name>.env]
Per-service env contract for inline services. In delegated layouts and single-service apps, use the same fields in a top-level [env] block inside that service’s floo.service.toml.
| Field | Type | Default | Description |
|---|
required | string array | [] | Env var names that must be set before deploy |
optional | string array | [] | Env var names documented for the service but not required |
managed | string array | implicit legacy mode | Managed service credentials to inject into this service: postgres, redis, storage, or named handles such as postgres:analytics |
[services.web.env]
managed = []
[services.api.env]
required = ["STRIPE_SECRET_KEY"]
managed = ["postgres", "redis"]
If no service declares managed, managed service credentials are injected into every service for backward compatibility. Once any service declares managed, services without it receive none.
[service] (floo.service.toml)
Used only in the delegated multi-service layout, where each service keeps its own floo.service.toml alongside its code.
| Field | Type | Default | Description |
|---|
name | string | required | Service name (DNS-safe, 2-21 chars) |
type | string | required | web, api, worker |
port | integer | required | Port the service listens on |
ingress | string | "public" | public or internal |
env_file | string | none | Relative path to env file synced on deploy |
[resources]
| Field | Type | Default | Description |
|---|
cpu | string | "1" | vCPU allocation |
memory | string | "512Mi" | Memory allocation |
min_instances | integer | 0 | Min instance count (0 allows scale to zero) |
max_instances | integer | 10 | Max instance count |
Per-service resource fields override the global [resources] section.
[managed.<name>]
Declare a managed service in config. <name> is a logical instance name — declare more than one block to run multiple services of the same type (e.g. [managed.primary] and [managed.analytics]). A deploy provisions any declared service that doesn’t exist yet; it never destroys one. Removing a block leaves the service running and surfaces an orphan warning in floo preflight — destroy it explicitly with floo services remove. The imperative equivalent is floo services add <type> --name <name>.
| Field | Type | Default | Description |
|---|
type | string | required | postgres, redis, or storage |
tier | string | none | Accepted for backwards-compatibility but ignored — every managed service ships with the same defaults |
enabled | boolean | true | Set false to keep a declaration without provisioning it |
[managed.primary]
type = "postgres"
[managed.cache]
type = "redis"
Credentials are injected per-service via the [services.<name>.env] managed field above.
[postgres], [redis], [storage] (deprecated)
Deprecated in favor of [managed.<name>]. Top-level [postgres] / [redis] / [storage] sections still auto-provision on first deploy during the deprecation window, but every deploy that processes them emits a deprecation warning. Run floo services migrate to move an existing app onto CLI-managed state in .floo/services.lock — it is idempotent and has zero data impact.
If you’re still on the legacy authoring path, the sections are top-level in floo.app.toml (not nested under [services.*]).
| Field | Type | Default | Description |
|---|
tier | string | none | Accepted for backwards-compatibility but ignored — every managed service ships with the same defaults. See Managed Services → Capacity. |
[environments.<name>]
Per-environment overrides. <name> is the environment slug — dev and production are the two that exist out of the box.
| Field | Type | Default | Description |
|---|
access_mode | string | none | Parsed but not yet applied on push deploys. Schema-valid, but only [app] access_mode flows through to the running app today. See note below. |
Per-environment access_mode is currently a no-op on push deploys: the schema validates the value (so a typo is caught at preflight), but the server does not apply it. The reason is safety — applying env overrides through the same column as the global default would silently downgrade sibling envs, which is the failure shape the post-2026-04-30 doctrine exists to prevent. The fix is a deferred-apply column on the deploy row, tracked as a follow-up. Until that lands, set one mode under [app] and use floo deploy --access-mode <mode> --app <name> for any env-specific override.
[cron.<name>]
Scheduled jobs run by the platform. Declared as [cron.<name>] sections in floo.app.toml.
| Field | Type | Default | Description |
|---|
schedule | string | required | Cron expression, e.g. "0 9 * * *" for 9am UTC daily |
command | string | required | Shell command executed in the target service container |
service | string | required | Name of the service whose image runs the command |
timeout | integer | 300 | Maximum execution time in seconds |
Example:
[cron.daily-report]
schedule = "0 9 * * *"
command = "python -m reports.daily"
service = "api"
timeout = 600
[github]
Controls GitHub integration behavior for the connected repo.
| Field | Type | Default | Description |
|---|
deploy_on_push | boolean | true | When false, GitHub push webhooks do not trigger deploys — the agent deploys manually |
Precedence
When you run a command without --app, the CLI resolves the app in this order:
--app <name> flag
- Nearest
floo.service.toml
- Nearest
floo.app.toml
Access mode resolution:
[environments.<env>].access_mode
[app].access_mode in floo.app.toml
Resource precedence: per-service values override global [resources].
Validation
The CLI fails preflight if:
- service names are duplicated
- a service port is invalid
- an inline service also has a
floo.service.toml in its directory
- a multi-service app has no public service
- managed service sections are placed in
floo.service.toml instead of floo.app.toml