deploy
Builds and runs a local app folder as a running service with a URL, and rolls that service back to its previous image when a release needs undoing.
Instructions
Use when: the source is a local folder. Asynchronous — follow with wait_for_service.
Deploys that folder (build + run + URL), or rolls back with
``rollback=True``. The response carries ``next_step`` — a machine-shaped
``{tool, args, why}`` naming the very call to make next — because a 201
here means "the build was accepted", never "the app is up".
``path`` is read by the process serving this tool, so it works only on
the local ``nerdit mcp`` process; on a remote/HTTP connection it is
refused with ``mcp.local_path_unavailable``. Use ``write_app_files`` +
``deploy_app`` there, or ``deploy_git`` / ``deploy_template``.
``rollback=True`` needs no path and works over either transport.
If the app's ``nerdit.toml`` declares ``[ai.*]`` bindings, each is
injected at launch as ``NERDIT_AI_<NAME>_URL/_KEY/_MODEL``, and the
``default`` binding also sets ``OPENAI_BASE_URL/OPENAI_API_KEY/
OPENAI_MODEL`` — point the app's OpenAI SDK at these; no code change
needed. ``dry_run=True``
returns the build/config plan diff (env key names only, values never).
``nerdit.toml`` ``[deploy]`` keys — anything else is IGNORED and
reported back in the response ``hints``: ``name``, ``port``, ``gpus``,
``start``, ``health``, ``health_type`` (``http``|``tcp``),
``memory_limit``, ``cpu_limit``, ``volumes``, ``release``, ``cutover``,
``auto_deploy``, ``edge_auth``, ``build_settings``. Generated Node builds run
``scripts.build`` automatically;
override it through ``[deploy.build_settings]`` or use a Dockerfile.
``release`` runs once inside the new image before traffic moves; if it
fails the deploy settles ``failed`` and the image reverts, never the data —
whatever the migration already committed stays, so write idempotent
migrations. ``cutover`` null = zero-downtime swap
when eligible, false = same-port swap.
On a successful non-dry-run deploy the
response carries ``summary`` (app/status/version/public_url), ``hints``
(ordered one-liners, never empty) and ``next_step`` (the structured
follow-up call); a ``dry_run`` plan and a ``rollback`` response carry
none of the three — read a plan's advisories from its ``warnings``. On a
node in ``path`` proxy mode the app is served at ``/<name>/``, so a
frontend must be built with that base
path; in ``subdomain`` mode it is served at the root of its own
hostname and needs none — read ``capabilities.proxy.mode``.
``build_settings`` overrides
preset/install/build/start/node_version/package_manager/subdir/public_env.
``preset`` selects node/nextjs/python/dockerfile; null resets to
repository/default detection.
An existing Dockerfile retains precedence.
Omit it to preserve saved overrides; a null field resets to repository/default,
and ``build: false`` skips compilation.
``public_env`` passes public build-time variables (shown in clear in a dry
run; secret references are refused). Secret mounts are unsupported;
runtime env is unchanged.
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 |
|---|---|---|---|
| env | No | Runtime env for the container: values are literal strings, never resolved (a ``${secrets.*}`` reference is passed through as text); a null value deletes that key on redeploy. Secrets go through ``set_secret``, which overrides an ``env`` key of the same name at launch. | |
| gpus | No | GPUs to reserve for the container (0 = none). Omit to use ``[deploy].gpus``, else the count already recorded on a redeploy, else 0. | |
| name | No | App name (lowercase DNS label); an existing name redeploys it in place. Omit to use the folder's ``nerdit.toml`` ``[deploy].name``, else the folder's own name. Required with ``rollback=True``. | |
| path | No | Path to the app folder ON THE DAEMON HOST (``~`` expanded); its contents are zipped and uploaded. Required unless ``rollback=True``. Usable ONLY from the local ``nerdit mcp`` process: over a remote/HTTP connection to a daemon it is refused with ``mcp.local_path_unavailable``, since it would read that host's disk and not yours — send the source with ``write_app_files`` + ``deploy_app`` instead. | |
| port | No | Port the container listens on: the only port published and health-checked. Omit to use ``nerdit.toml`` ``[deploy].port``, else the port already recorded on a redeploy, else 8000. | |
| start | No | Start command run inside the built image (shell syntax). Omit to use ``[deploy].start`` or the buildpack default. | |
| health | No | HTTP health path probed on ``port`` (e.g. ``/health``); the app is ``healthy`` once it answers 2xx. Omit to keep ``[deploy].health`` (or the existing app's probe on redeploy). | |
| vendor | No | GPU vendor to reserve when ``gpus`` > 0 (``nvidia`` or ``amd``); omit to let the daemon choose on a fresh deploy — a redeploy keeps the vendor already recorded. | |
| dry_run | No | true = validate and return the plan (what would change) with zero writes; nothing is built, deployed or audited as a change. | |
| rollback | No | true = re-point the service at its previous image instead of building: only ``name`` and ``idempotency_key`` are read, no folder is zipped and every other argument is ignored. Refused together with ``dry_run``. | |
| build_settings | No | Build overrides, same keys as ``[deploy.build_settings]`` in ``nerdit.toml``: ``preset`` (node/nextjs/python/dockerfile), ``install``, ``build`` (``false`` skips compilation), ``start``, ``node_version``, ``package_manager``, ``subdir``, ``public_env`` (build-time variables baked into the bundle, never secrets). Commands run inside the build container. | |
| 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. |