Skip to content

Control-Plane Operations

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.


Terminal window
# 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

CommandDescription
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 migrateMigrate legacy NSELF_DEPLOY_HOST_* env vars to control-plane.yaml

Terminal window
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.

RolePurpose
appApplication containers
lbLoad balancer (routes to --upstreams)
observabilityLogging and metrics collectors
dbDatabase server
workerBackground job worker

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 prompts
  • StrictHostKeyChecking=accept-new — accept unknown hosts on first connect, reject mismatches
  • ForwardAgent=no — agent forwarding disabled

Terminal window
# 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.

ValueSSHDockerDeploy eligible
managereachableaccessibleyes
read-onlyreachableinaccessibleno
hiddenunreachablen/ano

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.

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

The deploy pipeline always processes servers in this order:

  1. observability — collectors start first
  2. app — application containers
  3. 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.


.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.


If you previously configured deployments via NSELF_DEPLOY_HOST:

Terminal window
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.


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-only or hidden
  • The Add Server modal calls nself env target add server-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>

Server shows read-only: Add the deploy user to the docker group on the remote host:

Terminal window
sudo usermod -aG docker ubuntu && newgrp docker

Then re-probe: nself env target probe staging:web1 --refresh

Server shows hidden: Test connectivity directly:

Terminal window
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.


  • nself env — environment switching, show, diff, copy
  • nself deploy — deploy to one or more servers in the fleet
  • nself env command reference