nebula.toml project file
Every table and key of the nebula.toml that declares a project's services, databases and connections, with limits, wiring rules and the problems the parser reports.
A project file is a nebula.toml at the root of a project's config repository. It declares the project's services and databases and how they connect, instead of configuring one service. It is told apart from a service file by a top-level [services] or [databases] table. A service file may hold neither. To set one up, follow Describe a project in Git.
Example
The example declares a Postgres database with two read replicas in production, a Valkey cache, an API that connects to the cache, and a web service that connects to the API.
schema = 1
[databases.db]
engine = "postgres"
version = "18"
size = "10Gi"
[databases.db.postgres]
readers = 0
[databases.db.environments.production.postgres]
readers = 2
[databases.cache]
engine = "valkey"
[services.api]
context = "services/api"
connect = ["cache"]
[services.api.env]
DATABASE_URL = "${{ db.DATABASE_URL }}"
[services.api.connection]
API_URL = "http://${{ host(api) }}:8080"
[services.api.deploy]
release_command = "bin/migrate"
[services.api.processes.web]
port = 8080
[services.api.environments.production.processes.web]
replicas = 3
[services.web]
context = "apps/web"
connect = ["api"]
[services.web.processes.web]
port = 3000
[services.web.env]
SESSION_SECRET = "${{ secret(64) }}"
[services.web.secrets]
STRIPE_API_KEY = "Stripe secret key: Dashboard, Developers, API keys"The wiring:
connect = ["cache"]givesapithe variableREDIS_URL_CACHE = ${{ cache.REDIS_URL }}.connect = ["api"]giveswebthe variableAPI_URL = ${{ api.API_URL }}.DATABASE_URLis a plain reference under the app's own name.readers = 0is the base, and production overlays it with two read replicas.
Validate a file
nebula config validate takes a project file like a service file, and detects it by its top-level tables.
nebula config validate nebula.tomlOffline, the command checks the shape of every table, every connect target and reference between the resources the file declares, each depends_on entry that names its own service, cycles, engine versions, and each service block resolved for the base, every environment the file overlays, and production, development and preview. A valid file prints one line per resource under the success line:
nebula.toml: ok (2 databases, 2 services)
databases.cache: valkey, newest version
databases.db: postgres, 18
services.api: builds services/api, connects cache
services.web: builds apps/web, connects apiA problem prints as <file>:<line>: <path>: <message>, with the dotted path from the root of the file. The exit status is 1 when there is any:
nebula.toml:5: services.web.connect: services.web connects to api, which publishes no connection variables; add [services.api.connection] or reference a key with ${{ api.KEY }}The command cannot know what the project already has. A reference to a slug the file does not declare is not checked offline, and neither is adoption. The control plane checks those when it reads the file.
Top level
schema
Type integer · Required · Constraints must be 1
The schema version. A file without it fails with schema: is required; write schema = 1 at the top of the file.
[services] and [databases]
Type tables of blocks, keyed by slug · Default none · Constraints at least one resource in total; 20 services and databases together
A project file holds only schema, [services] and [databases]. Any other top-level key is a problem. A service-file key at the top level gets a message that says where it goes, such as "build" is a service setting; in a project file write it inside a service, as [services.<slug>.build].
The parser reads the file under the rules of the service file: strict TOML 1.0 in UTF-8, and every problem collected in one pass. The file may be 256 KiB.
Slugs
The table key of a service or database is its slug. Services and databases share one set of slugs. A slug is lowercase letters and digits in single-hyphen-separated segments, starting with a letter, at most 40 characters. inputs is reserved. A slug used by both a service and a database is a problem. Renaming a key declares a new resource, and the old one is handled by the removal rule.
[services.<slug>]
A service block names its source and its connections, and may hold the tables of a service file.
context
Type string · Constraints exactly one of context and image; relative, no .., "." for the root
A git service built from the config repository, from this directory.
image
Type string · Constraints exactly one of context and image; no spaces
An image service. The reference's repository is the service's image, and the reference is what its first deploy runs, such as ghcr.io/acme/api:1.2. The first deploy happens only while the service has no running release in the environment. Later versions deploy as for any image service, from the console or a push trigger, and a change to the tag in the file changes nothing.
A service built from another repository is not declared. It stays managed in the console, and the file may still reference it.
connect
Type array of slugs · Default none · Constraints unique; never the service itself
Services and databases this service takes connection variables from. See Connections.
[services.<slug>.connection]
Type table of string to string · Default none · Constraints keys match ^[A-Z_][A-Z0-9_]*$; at most 100
The variables this service publishes to services that connect to it. Values may use references.
[services.<slug>.secrets]
Type table of string to string · Default none · Constraints keys match ^[A-Z_][A-Z0-9_]*$; at most 100
Variable keys the service needs and Git must not hold. Each value is a description of where to get the secret. See Variables the file owns.
build, deploy, env, processes, mounts, environments
Type the tables of a service file · Default none
These write inside the block, with the same keys, limits and checks: [services.<slug>.build], [services.<slug>.deploy], [services.<slug>.env], [services.<slug>.processes.<name>], [[services.<slug>.mounts]] and [services.<slug>.environments.<slug>]. A [deploy] depends_on names slugs of the file's resources or of the project's.
A service is configured by one file. A service declared here whose own context directory also holds a nebula.toml is a problem: services.api is declared in the project config, and services/api/nebula.toml also configures it; keep one of them.
Services in a build context
A build that finds a project file as the nebula.toml of its build context does not refuse the tables as unknown keys. A service with build context . reads the root file, so this rule applies to it. The build log records what happened in its config step:
- The file declares the service. The build uses the service's
[services.<slug>]block as its file, without[env], and logsusing [services.<slug>] of the project config. - The file does not declare the service. The build runs with no service file and logs
nebula.toml is a project config that does not declare <slug>; building without a service file. For the root file the message begins withthe root nebula.toml. - The file does not parse. The build fails before any job starts, like an invalid service file.
A declared service whose own directory has no nebula.toml reads the root file. A root file that is a service file never stands in for another service.
[databases.<slug>]
A database block creates the same database service the console creates. Postgres runs on CloudNativePG. The project file never describes a Postgres cluster itself.
engine
Type postgres, valkey, mysql or mongodb · Required
The engine NebulaCtrl runs.
version
Type string · Default the newest · Constraints an engine major that the control plane runs
An engine major such as "18". The control plane runs PostgreSQL 18 and 16, Valkey 9 and 7, MySQL 8, and MongoDB 7. An unknown version fails with postgres 15 is not a version this control plane runs; use one of 18, 16.
size
Type quantity string · Default 10Gi · Constraints greater than zero, at most 1Ti
The initial volume size. It is used only when the database is created. An existing volume keeps its size.
[databases.<slug>.postgres]
Type table with readers · Default none · Constraints allowed only when engine = "postgres"
The only engine-specific table. readers is the CloudNativePG reader count, an integer from 0 to 5. A valkey, mysql or mongodb table has no settings and is refused. A table for another engine than the database's own is refused.
[databases.<slug>.environments.<slug>]
Type table holding the engine table · Default none
Overlays the engine table for one environment, as in [databases.db.environments.production.postgres]. size, version and engine cannot be overlaid, because a database has one of each in every environment.
A database is a separate table so that a file can never give a database processes, an image or a command. Point-in-time recovery, backup schedules and promotion stay in the console, because point-in-time recovery names an object store credential.
Wiring
References
Any value in [env] or [connection] may use ${{ slug.KEY }}, ${{ secret(N) }} and ${{ host(slug) }}, which the same scanner reads as in a service variable. ${{ inputs.KEY }} is refused. See Variable expressions.
- A
${{ slug.KEY }}must name a service or database that the file declares or the project already has, and a key it will have. - A reference cycle is a problem:
variable references form a cycle: ...; replace one of them with a value. secret(N)draws N random letters and digits. N is 16 to 128 and defaults to 32.
Connections
connect = ["slug"] writes the target's connection variables into the consumer's service tier, each valued as a reference to the target. A database supplies its engine's variable, as connecting it in the console does. For PostgreSQL it also supplies the read endpoint.
| Engine | Variables written, with <SLUG> the slug in capitals and hyphens as underscores | Values |
|---|---|---|
postgres | DATABASE_URL_PG_<SLUG>, DATABASE_READ_URL_PG_<SLUG> | ${{ slug.DATABASE_URL }}, ${{ slug.DATABASE_READ_URL }} |
valkey | REDIS_URL_<SLUG> | ${{ slug.REDIS_URL }} |
mysql | DATABASE_URL_MYSQL_<SLUG> | ${{ slug.MYSQL_URL }} |
mongodb | DATABASE_URL_MONGO_<SLUG> | ${{ slug.MONGODB_URL }} |
A service supplies the keys of its [connection] table, under the same names, and those keys are also written as the target's own variables so that the references resolve. connect to a service that has no [connection] table is a problem: services.web connects to api, which publishes no connection variables; add [services.api.connection] or reference a key with ${{ api.KEY }}.
A reference or connect also decides the order of the first deploys: what a service reads or connects to deploys before it. See Deploy order.
Variables the file owns
Variables from [env], [connection] and connect are service-tier variables that the file owns while it names them. The console shows them read-only, labeled Project nebula.toml, and they become ordinary variables once the file stops naming them. The API refuses to change one with 409: KEY is set by the project's nebula.toml at 3f9c2ab; change it in the file and push, or remove it from the file to edit it here.
- A key written twice on one service is a problem, such as by
[env]andconnect. - A plain value over an existing secret that the file does not own is a problem, because a value from Git never replaces a secret. Write
${{ secret(32) }}to have a new one drawn, or keep setting the variable in the console. ${{ secret(N) }}is drawn once per environment, when the key is first written, and sealed. It is not drawn again while the key stays in the file.- A
[secrets]key is created once, empty and sealed, in each environment that lacks it. It is never the file's: set the value in the console. A key that[env],[connection]orconnectalso writes is a problem. The Variables tab marks an empty variable, and a deploy never waits for it.
What the file never names
Domains, secret values, the cluster binding, the deployment strategy, production approval rules and point-in-time recovery stay in the console. Domains reach DNS and Cloudflare, so a push does not change them.
Limits
| Limit | Value |
|---|---|
| File size | 256 KiB |
| Services and databases together | 20 |
| Slug | 40 characters |
readers | 0 to 5 |
size | greater than zero, at most 1Ti |
[connection] and [secrets] keys | 100 each |
The limits of a service file apply inside each service block.
Problems
A problem is {path, line, message}, with dotted paths from the root, such as services.api.env.DATABASE_URL or databases.db.postgres.readers. Besides the shape problems above, nebula config validate refuses a file when:
- A
connecttarget, reference orhost(slug)names a resource that neither the file declares nor, on the control plane, the project has, or a key that it will not have. - A
depends_onslug names itself or, on the control plane, a resource that does not exist. A list that closes a cycle with other lists, theconnecttargets and the references is refused:api → worker → api would never finish deploying: remove worker from [services.api.deploy] depends_on, or the entry for api in [services.worker.deploy] depends_on. - A database asks for an engine or a version that the control plane does not run.
- The file declares nothing:
the file declares nothing; add a [services.<slug>] or a [databases.<slug>].
The control plane also refuses a file when it meets what the project already has:
- A service or database exists with the slug but another kind:
db is a valkey database here, but nebula.toml declares it as postgres; rename one of them. - A database runs another major:
db runs postgres 17 here; nebula.toml asks for 18; a database's data needs the version it was created with, so restore a dump into a new database. A file that sets noversionnever conflicts. - A service has another source:
api builds from acme/api here, but nebula.toml builds it from services/api of the config repository; change the service's source in its settings, or rename one of them. - A plain value would replace a secret:
KEY is a secret variable on api that nebula.toml does not own; ....
Only the control plane can find these, because only it knows the project. nebula config validate does not.
nebula.toml service file
Every table and key of the nebula.toml that configures one service from its repository, with types, defaults, limits and the problems the parser reports.
Template file
Every field of a template YAML file, its expressions, limits and the problems nebula template validate reports.