Skip to content
NebulaCtrldocs
Guides

Describe a project in Git

Declare a project's services, databases and connections in a root nebula.toml, point the project at the repository, and review the plan each push stages.

A project file declares a project's services and databases and which of them connect. NebulaCtrl reads it at every push to a branch that an environment tracks, and stages what it adds or rewires as one change set per environment. A production environment waits for an approval. The project file reference lists every key.

This is a different file from the nebula.toml of a single service. A top-level [services] or [databases] table marks a project file. Compose import is the one-time alternative: it stages services once and writes no file.

Before you begin

  • A repository that holds the file at its root, and the admin role to set it on the project. To connect a repository, see Git providers and registries. A public repository needs no connection, but a public URL is read only on github.com, gitlab.com, codeberg.org or a Forgejo or Gitea host.
  • A project with at least one persistent environment. Each one's Branch, set under Environment settings, decides which pushes are read. Preview environments are not planned.

Write the file

Commit a nebula.toml at the root of the repository. This one declares a database, an API that connects to it and a web service that connects to the API.

nebula.toml
schema = 1

[databases.db]
engine = "postgres"

[services.api]
context = "services/api"
connect = ["db"]

[services.api.connection]
API_URL = "http://${{ host(api) }}:8080"

[services.api.processes.web]
port = 8080

[services.web]
context = "apps/web"
connect = ["api"]

[services.web.processes.web]
port = 3000

[services.web.secrets]
STRIPE_API_KEY = "Stripe secret key: Dashboard, Developers, API keys"

schema = 1 is required. The table key of every service and database is its slug. A service names one source: context, a directory of this repository to build, or image, a reference to run. A service built from another repository is not declared; it stays managed in the console, and the file can still reference it.

Run the check before you push. It needs no control plane and reads ./nebula.toml when you give no file:

nebula config validate
./nebula.toml: ok (1 databases, 2 services)
  databases.db: postgres, newest version
  services.api: builds services/api, connects db
  services.web: builds apps/web, connects api

The command cannot know what the project already has, so the control plane checks references to other resources and the adoption of existing ones at a push.

Point the project at the repository

To start a project from the repository, select New project, enter a Name, choose From a repository under Start with, and pick the repository. The dialog reads the default branch and lists Use nebula.toml first when the root file is a project file, with a line such as 1 service (homelab) and 1 database (db). The button then reads Create with nebula.toml. It creates the project, sets the repository as its config repository and reads the file at once, and the services appear on the canvas. A repository with a project file, a Compose file and a Dockerfile still selects Use nebula.toml first; Import docker-compose.dev.yml and Dockerfile only stay listed. If the file has problems, the dialog shows them and does not offer it. See Deploy from Git.

To set the repository on an existing project:

  1. Right-click the project's card, or an empty part of the canvas, and select Project settings…. Then find Config repository.
  2. Select Set repository. If the project already has a repository, select Change.
  3. Choose Connected repository and pick the connection and repository, or choose Public URL and enter the repository's https:// URL.
  4. Select Set repository.

NebulaCtrl first checks that it can read the branch tracked by the project's first persistent environment, non-production environments first and then by slug. If it cannot, the form shows why and the project keeps its previous repository. Otherwise it stores the repository. The summary shows how pushes reach the project:

SummaryMeaning
Pushes arrive through the GitHub AppA GitHub App connection already receives every push
Webhook installedNebulaCtrl created a push webhook for the project on Forgejo or GitLab, with its own secret
Webhook needs adding by handThe token could not create the webhook. A warning titled The repository is stored, but its webhook was not created shows the webhook URL and the secret once. Copy them before you select Dismiss, and add the webhook to the repository. Until it exists, pushes are not seen: select Read now after a change
No webhookA public URL has no webhook. Select Read now after a change

Nothing is read until a push arrives or you select Read now. Replacing the repository with a different one ends what the old one configured, as removing it does; see Remove the config repository.

When you add a service from a repository whose root nebula.toml is a project file, the dialog shows This repository describes a whole project. Its Set as config repository button opens this form with the repository filled in.

Read the file

Push to a tracked branch, or select Read now in Config repository. Tracked branches then lists each branch with its commit and a badge: Read, Has problems, Could not be read or No nebula.toml. A branch with problems lists each as path (line N): message.

On a push, the control plane reads the root nebula.toml at the pushed commit before any build of that push starts.

  • A file that does not parse, whose references do not check, or that cannot adopt what the project has plans nothing. Every service built from the config repository skips the build of that push, including console-managed services the file does not declare, so a broken file never ships a half-wired project. A banner above the canvas gives a one-line summary: Show lists the problems, and Open the config repository opens Config repository. Fix the file and push again.
  • A file the host could not deliver (Could not be read) also blocks those builds. The control plane retries the read, and the next push or Read now tries again.
  • A repository with no nebula.toml (No nebula.toml) blocks no build. The plan stages every service the file declared earlier as No longer in nebula.toml, as for a removal.
  • A root nebula.toml that is a service file, with no [services] or [databases] table, plans nothing and blocks no build. The branch shows Has problems, and the message tells you to add the tables or remove the config repository.
  • A valid file is planned. The plan creates every declared service and database the project lacks, with the same creation the console uses, and stages the rest. A created resource exists in every environment and runs nowhere until a change set deploys it.

Review the plan

Latest plan per environment shows one plan per environment with a badge:

BadgeStateMeaning
Nothing to changeunchangedThe environment already matches
Staged, not appliedstagedA change set is staged and waits for a person
Waiting for approvalpending-approvalA production approval is open
AppliedappliedThe change set was applied
FailedfailedThe automatic apply was refused, or the applied change set did not finish deploying
Replaced by a newer commitsupersededA later valid push replaced it
RefusedrefusedThe file was invalid, or the plan was refused. Nothing was staged
DiscardeddiscardedA person discarded the staged change set
RejectedrejectedA person rejected the approval

Each environment's plan is one change set, applied with the message nebula.toml @ <short sha>. It holds that environment's first deploys (databases first), variable writes, reader counts and per-service file blocks. A review shows what the commit adds and rewires. An environment that already matches stages nothing. Select Open the change set on a staged or waiting plan to review it.

The change set belongs to the project, not to a person: anyone who may deploy in the project can review, apply or discard it. Select Apply or Discard in the review.

  • A non-production environment applies it at once. If the apply is refused, for example because it needs downtime accepted or a value drifted, the plan reads Failed with the reason and the change set stays staged. The review shows nebula.toml was not applied automatically. Deal with the reason, or tick Accept downtime, then apply it yourself or discard it.
  • A newer valid plan replaces one that is not applied. A staged change set is discarded, and a pending approval is rejected as superseded by the newer commit.

Production waits for a person

A protected environment always waits. The push submits the change set for approval itself, so the plan reads Waiting for approval. Where an apply button shows, it reads Request approval, and the change set is never approved on the spot: a service's skip-approval setting does not apply, because nobody turned on anything this commit adds. During a deploy freeze the approval is still created, and deciding it is refused until the freeze ends. A push never creates or rewires anything in production without a decision. See Approvals and freezes.

Use an existing resource

A service or database with the same slug and kind as a declaration is adopted, and its settings are then read from the file. Kind means service or database, with the same engine for a database. For a service it also means the same source: a git service building this repository at the same directory, or an image service of the same image repository.

Anything else is refused with what to do: a different kind or engine, a different engine major, or a different source. The messages are listed in the reference. A database's data needs the version it was created with, so a changed version means restoring a dump into a new database under a new slug.

Set secrets, order and variables

  • Secrets you set yourself. List a variable under [services.SLUG.secrets]. Each key the environment lacks is created once, empty and sealed. Set the value in the console. The file never carries it.
  • Order. What a service references or connects to deploys before it. For an order no variable shows, add depends_on = ["api"] under [services.SLUG.deploy]. A depends_on in the file replaces the console's Deploy after list. See Deploy order.
  • Variables. [env], [connection] and connect variables are read-only in the console while the file names them, and carry a Project nebula.toml badge. See Variables.

Remove a resource

Removing a resource from the file never deletes it. When the change set applies, a removed service becomes console-managed in each environment where it was declared, and its variables stay with their last values and become editable. The change set shows the item as No longer in nebula.toml, and a production environment waits for an approval like for any other change. Removing a database from the file changes nothing about the database.

Deleting a resource stays a person's decision, because a delete takes its volumes, backups and archives with it.

Remove the config repository

In Config repository, select Remove… and confirm with Remove. Every variable the file owned becomes ordinary and editable, and every service and database it declared stays, managed in the console. Staged change sets are discarded, waiting approvals are rejected, the branches read and plans made from the repository are forgotten, and the project's webhook is removed. Nothing is deleted, and you can set a repository again later.

Over the API

Method and pathActionPurpose
GET /api/v1/projects/{id}/configproject.readThe config repository, each tracked branch's last read with its status and problems, and the newest plans
PUT /api/v1/projects/{id}/configproject.writeSet the repository
DELETE /api/v1/projects/{id}/configproject.writeRemove the repository
POST /api/v1/projects/{id}/config/readproject.writeRead every tracked branch's head now and plan it

Set a connected repository with {"source": {"kind": "git", "gitConnectionId": "CONNECTION_ID", "repository": "acme/shop"}}, or a public one with {"source": {"kind": "public-git", "gitUrl": "https://github.com/acme/shop"}}. GET and PUT return the repository with a webhook of github-app, installed, manual or none. A branch's status is ok, invalid, absent or unreadable. A plan's state is one of the nine states above, and it carries the changeSetId and the commitSha it was made from. GET returns at most the 50 newest plans.

A change set made from a project file has source: "project-config" and sourceCommit, and no owner. GET /api/v1/environments/{id}/change-set returns the open one in projectConfig to a caller who may deploy, and apply and discard work as for any change set. To look at a repository before pointing a project at it, send POST /api/v1/repository-config/detect with the repository and a branch: it answers status project for a valid root project file, with the services and databases it declares. See the project config endpoints, the change set endpoints and the HTTP API.

Verify

In Config repository, Tracked branches lists each tracked branch with the badge Read, and Latest plan per environment shows a badge for every environment. A plan that changed something reads Applied in a non-production environment and Waiting for approval in production. A plan for an environment that already matches reads Nothing to change.

Next steps

On this page