Skip to main content

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

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

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

RolePurpose
appApplication containers
lbLoad balancer (routes to --upstreams)
observabilityLogging and metrics collectors
dbDatabase server
workerBackground 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 prompts
  • StrictHostKeyChecking=accept-new — accept unknown hosts on first connect, reject mismatches
  • ForwardAgent=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

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.

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:

  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.


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

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, copy
  • nself deploy — deploy to one or more servers in the fleet
  • nself env command reference