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.3. Set one secret
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.
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
ACMD 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:
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
mode managedmeans the API key was read.mode localmeans it was not, and nothing will sync.addressis what you asked for.127.0.0.1when you expected0.0.0.0means the certificate branch above did not fire.session tokenis 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.
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.