Skip to main content
This page is the sequence. Governance server is the reference for everything it touches: certificate operations, token rotation, port behaviour, server management. Follow the steps here, and read that page when a step raises a question.

1. Decide the shape

There are two, and the choice decides everything after it. Start with one container if you can. Not everything has to move at once: a second process joins later without the first one changing shape, and the rule set it is evaluated against is the same one.
--serve is how a governed app starts, here and on your laptop. On loopback it needs no certificates and no setup, so there is nothing to trade away by using it everywhere, and it is the shape verified against a real container, a real port, and a real restart. Using it in development too means the thing you tested is the thing you deploy.

2. Install dependencies at build time

A governed run should not fail on something unrelated to its policy. The allowlist governs your app’s tool calls, and a package installer running inside a governed process is a surprising way to discover that.
--locked, not --frozen. Both refuse to update the lock file; only --locked first checks that the lock still agrees with pyproject.toml. With --frozen, a lock left behind by an edited pyproject.toml installs anyway, so an image can ship an older nanny-sdk than the one you asked for and fail at runtime on a symbol that exists in the version you thought you had. npm ci and pip install -r already fail this way by default.

3. Set one secret

Create it in the dashboard under Settings → API Keys, then set it however your platform sets secrets: a Fly secret, a Kubernetes Secret, a Coolify environment variable. That single variable decides whether runs sync; see Connect to Nanny Cloud. The key’s prefix (nny_live_ or nny_sdbx_) also decides whether this deployment writes real evidence or throwaway sandbox data. Read Live and sandbox before deploying against a live key for the first time. Leave it unset and everything still works. Enforcement is local and never depends on the cloud.
Do not bake the key into an image or commit it. It is an ordinary deployment secret and belongs with the rest of them.
Set it on the governor only. Joined processes report through the governor, and a second key would split the audit trail and count the same run twice.

4. If anything joins from elsewhere, do this before you deploy

Skip to step 6 for a single container. Generate the bundle on your machine, never in a container, and mint the token at the same time:
--san must cover the name the joiner dials. The client verifies the name it dialled against that list, and a mismatch fails the handshake before any request is made. Then mount, rather than paste: server.crt, server.key and ca.crt into the governor, and client.crt, client.key and ca.crt into each joiner. A value in the environment is readable through /proc/<pid>/environ and inherited by every child process, where a mounted file can be 0600 and is inherited by nothing. Only files rotate, too: an environment variable cannot change in a running process, so certificates supplied that way are replaced on the next restart instead of in place. ca.key never leaves your machine. It signs certificates, so anyone holding it can mint a client the governor will trust. The session token is the exception and is always an environment variable, on both sides. Both ends read the same variable, so a second form to interpret is only a way for them to disagree. See Rotating the token for moving a fleet onto a new one without stopping it.

5. One image, both shapes

A CMD with certificate paths baked into it only runs with certificates, so a single-container deploy needs a second image. Deciding at boot from whether the certificates are present gets both shapes out of one:
The two branches match how the runtime already behaves: it picks its transport from the address, serving plain HTTP on loopback and requiring mTLS anywhere else. So binding 0.0.0.0 without certificates would fail to start, and binding loopback in a container makes the governor unreachable by anything but itself. Adding the certificates is what opens it to the joiners.
exec is what makes this safe. Nanny must be PID 1 so it receives SIGTERM directly, drains for up to 10 seconds, and stops the app it launched. A shell that stays running as the parent breaks that: sh does not forward signals to its children, so the governor never drains and nothing notices if one half dies while the other keeps running half-governed. exec replaces the shell rather than leaving one in front, so a script that ends in exec nanny is exactly as correct as a plain CMD.

6. Read the first boot

Four things worth checking, in order:
  • mode managed means the API key was read. mode local means it was not, and nothing will sync.
  • address is what you asked for. 127.0.0.1 when you expected 0.0.0.0 means the certificate branch above did not fire.
  • session token is a fingerprint, never the token: eight characters from the head, four from the tail, and the length. Enough to confirm the governor took the token you set and not a stale one, and not enough to use, so a log aggregator holding it forever costs you nothing. Compare it against what you set on the joiners. Under mTLS the governor also prints the variables a joining process needs.
  • The launch line names the transport. (mTLS) here and (plain HTTP, loopback) are the two you will see.
Then nanny status from the same directory, or the dashboard, which lists the app by the name in .nanny/app.json. Start order does not matter. A joiner started before its governor retries its first connection for 30 seconds rather than failing the work it was handed. Once it has connected, a later failure stops the run immediately: waiting for a first connection is patience, waiting mid-run is running ungoverned.

7. When it does not come up

8. After it is up

/health is served without the session token and reports whether the run is up and nothing else, so an orchestrator can probe it:
/status carries call counts, history and the stop reason, and stays behind the token. There is no login step and no state to carry between deploys. A fresh container behaves the same as one that has been running for a month, and twenty replicas behave the same as one. If the cloud is unreachable, events are held under .nanny/spool/ (partitioned by environment) and delivered on the next run. If your platform gives the container a writable filesystem this is automatic; if it does not, a cloud outage costs those events, and everything else still works.