Control-Plane Operations
Manage a multi-server fleet with nself env target — inventory, SSH probing, capability classification, and deploy pipeline ordering.
nself env target manages a multi-server deployment fleet stored in .nself/control-plane.yaml.
The file is written at mode 0600 and never committed to version control.
The companion Admin UI page (localhost:3021/environments) mirrors these commands and provides
a visual capability matrix without requiring terminal access.
Quick Start
# Add the primary app server for staging
nself env target add staging web1 \
--host ubuntu@staging.example.com \
--role app \
--key-ref NSELF_SSH_KEY_STAGING \
--primary
# Probe capability
nself env target probe staging --refresh
# List the inventory
nself env target list
Subcommands
| Command | Description |
|---|---|
nself env target list [env] | List servers in the inventory (all environments, or filtered by name) |
nself env target add <env> <name> | Add a server to an environment |
nself env target remove <env> <name> | Remove a server from an environment |
nself env target probe [env[:server]] | Run SSH + Docker probes and print a capability matrix |
nself env target migrate | Migrate legacy NSELF_DEPLOY_HOST_* env vars to control-plane.yaml |
Adding Servers
nself env target add <env> <server-name> \
--host <user@host> \
--role <app|lb|observability|db|worker> \
--key-ref <ENV_VAR_NAME> \
[--remote-path /opt/nself] \
[--primary] \
[--upstreams name1,name2]
--key-ref takes an environment variable name (e.g. NSELF_SSH_KEY_STAGING), not a file
path or inline key. The actual path is read from that env var at runtime. Passing a file path
or PEM block is rejected.
--primary marks one server per environment as the primary app server. Primary servers
receive one-off commands (migrations, health checks) when no specific --server flag is given
to nself deploy.
Server roles
| Role | Purpose |
|---|---|
app | Application containers |
lb | Load balancer (routes to --upstreams) |
observability | Logging and metrics collectors |
db | Database server |
worker | Background job worker |
SSH Key Setup
Store actual key paths in .env.secrets (not committed):
# .env.secrets
NSELF_SSH_KEY_STAGING=/home/ops/.ssh/nself_staging_ed25519
NSELF_SSH_KEY_PROD=/home/ops/.ssh/nself_prod_ed25519
Key files must be readable by the user running nself. All SSH connections use:
BatchMode=yes— no interactive promptsStrictHostKeyChecking=accept-new— accept unknown hosts on first connect, reject mismatchesForwardAgent=no— agent forwarding disabled
Probing Capability
# Probe all servers
nself env target probe
# Probe one environment, bypass cache
nself env target probe staging --refresh
# Probe one server, JSON output
nself env target probe staging:web1 --json
Results are cached for 60 seconds. Use --refresh to force a live check.
Capability values
| Value | SSH | Docker | Deploy eligible |
|---|---|---|---|
manage | reachable | accessible | yes |
read-only | reachable | inaccessible | no |
hidden | unreachable | n/a | no |
A server is read-only when SSH connects but the deploy user cannot access
/var/run/docker.sock. Fix: add the user to the docker group on the remote host.
A server is hidden when SSH times out or is refused. Check firewall rules, key
authorization, and server uptime.
Table output example
ENV SERVER CAPABILITY SSH DOCKER LATENCY REASON
staging web1 manage ok ok 42ms
staging lb1 read-only ok - 38ms docker socket permission denied
prod web1 manage ok ok 91ms
Deploy Pipeline Order
The deploy pipeline always processes servers in this order:
- observability — collectors start first
- app — application containers
- lb — load balancers start last (no traffic until app is healthy)
LB servers run a drain hook (bin/nself-lb drain via SSH) before their containers are updated.
If the helper binary is absent (first install), the pipeline continues. If drain fails, the
deploy for that server is aborted.
Inventory File Format
.nself/control-plane.yaml (mode 0600):
version: 1
environments:
staging:
name: staging
kind: remote
servers:
- name: web1
role: app
host: ubuntu@staging.example.com
ssh_key_ref: NSELF_SSH_KEY_STAGING
remote_path: /opt/nself
primary: true
- name: lb1
role: lb
host: ubuntu@lb.example.com
ssh_key_ref: NSELF_SSH_KEY_STAGING
remote_path: /opt/nself
upstreams: [web1]
Do not commit this file. Add .nself/ to .gitignore.
Migrating from Legacy Env Vars
If you previously configured deployments via NSELF_DEPLOY_HOST:
nself env target migrate
nself env target list
The command is idempotent: running it again on an existing control-plane.yaml applies schema
updates without overwriting manually added entries.
Admin UI
The Environments page at localhost:3021/environments provides a visual view of the inventory:
- Server cards show name, role, primary tag, and capability badge
- A partial-access banner appears when any server is
read-onlyorhidden - The Add Server modal calls
nself env target addserver-side - The Refresh button re-probes all servers and updates the display
- Each environment card shows the CLI command to deploy:
nself deploy <env> --server <name>
Troubleshooting
Server shows read-only:
Add the deploy user to the docker group on the remote host:
sudo usermod -aG docker ubuntu && newgrp docker
Then re-probe: nself env target probe staging:web1 --refresh
Server shows hidden:
Test connectivity directly:
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
-i $NSELF_SSH_KEY_STAGING ubuntu@host echo ok
Check firewall rules, key authorization, and server uptime.
Probe returns stale data:
Bypass the 60-second cache: nself env target probe --refresh
Deploy blocked by LB drain failure:
Check that bin/nself-lb exists at --remote-path/bin/nself-lb on the LB server
and that the LB process is healthy.
See Also
nself env— environment switching, show, diff, copynself deploy— deploy to one or more servers in the fleet- nself env command reference