apply_project
Deploys every service declared in a project's nerdit.toml; use dry-run to validate and preview changes before applying.
Instructions
Apply a project's declaration: deploy every [services.<name>] of its nerdit.toml.
The loop: ``get_project`` (read) → ``apply_project(dry_run=true)`` →
if ``status`` is ``waiting_for_variables``, do NOT invent or request
the values in chat: ask the human to run ``nerdit vars set <project>
--secret --prompt KEY`` for each name in ``missing``, then dry-run
again → ``apply_project`` → ``wait_for_service`` once per ``label`` in
``services`` → ``diagnose_service`` on any that failed.
The response lists ``services`` in declaration order, each with its
``label`` (the name every other tool takes: ``web`` is the project
name, any other service ``<service>--<project>``), ``action``
(``fresh``/``redeploy``) and, on a dry run, its ``plan`` (env key names
only, values never); ``public_urls`` carries every advertised address.
``waiting_for_variables`` deploys nothing and returns names only.
Asynchronous like every deploy: ``applied`` means accepted, not healthy.
Every refusal lands before the first build, and a dry run creates
nothing and mints no idempotency key. A project another token owns is
409 ``project.owned``. There is NO rollback across services: a failure
on one is ``project.apply_incomplete``, whose ``services`` lists the
ones already applied and ``failed`` the one that was not — fix it and
apply again (done services redeploy, the rest are retried). A
``[deploy]``-only ``nerdit.toml`` is not a declaration: use
``deploy_app`` / ``deploy_git`` for it (they answer 422
``deploy.use_apply`` on a declaration).
SANDBOX: by DEFAULT containers run with all Linux capabilities dropped
(``cap_drop=["ALL"]``) and no-new-privileges — both are operator settings
(``[containers].drop_all_caps`` / ``[containers].no_new_privileges``), so
read the live profile in ``capabilities.sandbox`` rather than assuming: an
operator may have relaxed it. Under that default, an image whose entrypoint
does root work — ``chown``/``chmod`` of its own runtime dirs, dropping
privileges via ``setuid``/``setgid``, writing ``/var/run`` — dies before
your app ever starts. Stock ``nginx`` is the classic trap. Use a non-root
image and a non-root ``USER`` that owns the files it writes: for a static
site, ``nginxinc/nginx-unprivileged`` (listens on **8080**, so set
``port=8080``) or any tiny static server. A crash from this reports
remediation code ``image_needs_privileges`` in ``diagnose_service``. PORT
CONTRACT: the container MUST listen on the declared ``port`` (the tool
argument, else ``nerdit.toml`` ``[deploy].port``, else **8000**) — that is
the only port the daemon publishes and health-checks, so listening anywhere
else fails the health probe with a healthy-looking container. A port below
1024 is NOT the reason a root entrypoint is needed: every container runs on
the docker bridge in its own network namespace (never host networking),
where Docker sets ``net.ipv4.ip_unprivileged_port_start=0``, so binding 80
needs no capability and no root — the chown/setuid work does.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ref | No | Git branch or tag to clone; only with ``repo_url``. | |
| dry_run | No | true = validate and return the plan (what would change) with zero writes; nothing is built, deployed or audited as a change. | |
| project | Yes | Immutable prj_ ID or name of the project to apply. The resolved name must equal ``[project].name`` in ``nerdit.toml``. An unknown name may be created; an unknown or retired ID is refused. | |
| repo_url | No | https Git URL whose root ``nerdit.toml`` is the declaration. Omit to apply the project's workspace (what ``write_project_files`` wrote). | |
| token_ref | No | Private-repo credential as a REFERENCE, never a token: ``${vars.KEY}``, ``${secrets.shared.KEY}`` or ``${github.installation}``; only with ``repo_url``. | |
| idempotency_key | No | Caller-chosen key: a retry with the same key replays the first result instead of acting twice. Omit and a fresh key is minted per call, so each call acts. |