Skip to main content

Manifest Reference

The manifest system lets you declare your entire infrastructure in a single YAML file. DockNimbus provisions resources in dependency order, handles idempotent re-applies, detects configuration drift, and can prune orphaned resources.

CLI Usage

# Apply a manifest
nimbus manifest apply --file infra.yaml

# Apply with variable substitution
nimbus manifest apply --file infra.yaml --env S3_PASSWORD=secret --env NODE_IP=10.0.0.1

# Apply and prune resources that were removed from the manifest
nimbus manifest apply --file infra.yaml --prune

# Tear down all resources declared in the manifest
nimbus manifest remove --file infra.yaml

Flags

FlagDescription
--filePath to the manifest YAML file (required)
--envVariable substitution in KEY=VALUE format (repeatable)
--pruneDelete resources owned by this manifest that are no longer declared (requires name)

Manifest Structure

A manifest has the following top-level sections, each optional. Resources are provisioned in this order and removed in reverse:

nodes -> volumes -> swarms -> s3 -> kubernetes -> compute -> services

name

An optional identifier for the manifest. Required when using --prune to track resource ownership.

  • Must be 63 characters or less
  • Only alphanumeric characters and hyphens allowed
name: my-infrastructure

nodes

Bare metal machines to register as DockNimbus nodes. Each node is provisioned via SSH — the agent binary is installed, an mTLS certificate is issued, and WireGuard is configured automatically.

nodes:
web1:
ip: 192.168.1.10
name: web-server-1 # Optional display name
ssh:
profile: prod-servers # Use a stored SSH profile (recommended)
# user: root # SSH user (default: root)
# key: ~/.ssh/id_rsa # SSH private key path (default: auto-detect)
# password: ${PASS} # Password auth (alternative to key)
# port: 22 # SSH port (default: 22)

web2:
ip: 192.168.1.11
ssh:
profile: prod-servers

The alias (e.g. web1, web2) is used to reference this node in other sections. On re-apply, existing nodes matching the IP are skipped.

SSH Profiles

SSH profiles are named credential sets stored encrypted in the database. They solve the problem of the API server not having access to your local SSH keys when processing manifests server-side.

Create a profile once:

nimbus ssh-profile create --name prod-servers --user deploy --key ~/.ssh/id_ed25519

Then reference it in any manifest:

ssh:
profile: prod-servers

Inline SSH fields (user, port, key, password) override profile values when both are set. If only profile is specified, all credentials come from the stored profile.

See the CLI Reference for full details.

volumes

NFS volumes hosted on a node. A directory is created and exported via NFS so it can be mounted by compute instances or Kubernetes clusters.

volumes:
data-vol:
node: web1 # Node alias from the nodes section
folder: /srv/nimbus/data # Path on the node

Volumes are immutable after creation — changing the node or folder of an existing volume will produce an error to prevent accidental data loss.

swarms

Docker Swarm clusters. The first node becomes the manager; additional nodes join as workers.

swarms:
prod:
nodes: [web1, web2]
lb: true # Deploy EasyHAProxy load balancer
# cloudflare: # Optional external DNS
# token: ${CF_TOKEN}
# domain: example.com

On re-apply, node membership is reconciled — nodes added to the list are joined and nodes removed from the list are evicted.

s3

MinIO S3-compatible object storage instances deployed to a swarm.

s3:
store1:
swarm: prod # Swarm alias
volume: data-vol # Volume alias for persistent storage
password: ${S3_PASSWORD} # MinIO root password
# license: /path/to/license # Optional MinIO license file
# certs: /path/to/certs.pem # Optional custom TLS certificates

kubernetes

K3s Kubernetes clusters. The first node becomes the control plane; additional nodes join as agents.

kubernetes:
dev-k8s:
nodes: [web1, web2]
ha: true # Enable HA with embedded etcd (required for promote/demote)
volumes: # NFS volumes to attach as PV + PVC
data-vol: 1Gi
manifests: # Inline K8s resources applied after the cluster is ready
- apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 1
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: app
image: nginx:latest
ports:
- containerPort: 80
FieldDescription
nodesNode aliases — first is control plane, rest are workers
haEnable HA mode with embedded etcd (default: false). Required for promoting workers to control-plane. Uses more resources than single-server (SQLite) mode
volumesMap of volume alias to PVC size — creates NFS-backed PersistentVolume and PersistentVolumeClaim
manifestsInline Kubernetes resources applied via kubectl apply after cluster creation

On re-apply, cluster node membership is reconciled (the control plane node cannot be removed).

compute

Container instances deployed to either a Docker Swarm or a Kubernetes cluster. Specify exactly one of swarm or k8s.

compute:
web-app:
swarm: prod # Deploy to Docker Swarm
image: byjg/static-httpserver
type: small
replicas: 1
domain: app.example.com
ports:
- "80:8080" # host:container
volumes:
- data-vol:/data # volume-alias:mount-path
env:
- TITLE=soon
- "MESSAGE=Keep In Touch"
command: ["nginx", "-g", "daemon off;"]

k8s-app:
k8s: dev-k8s # Deploy to Kubernetes cluster
image: nginx:latest
type: medium
ports:
- "80:80"
FieldDescription
swarmSwarm alias to deploy to (mutually exclusive with k8s)
k8sKubernetes cluster alias to deploy to (mutually exclusive with swarm)
imageContainer image
typeInstance type: nano, micro, small, medium, large, xlarge
replicasNumber of replicas (default: 1)
domainCustom domain for the instance (routed via load balancer)
portsPort mappings in "host:container" format
volumesVolume mounts in "volume-alias:/mount/path" format
envEnvironment variables in KEY=VALUE format
commandContainer command as an array (e.g. ["sh", "-c", "echo hello"])
platformTarget platform constraint (e.g. arm64)
gpuNumber of NVIDIA GPUs to request (default: 0)

On re-apply, changes to image, type, domain, ports, env, command, or volumes are detected and the instance is recreated. Replica count changes are applied in-place via scaling.

services

Docker Compose stacks deployed to a swarm. Services are always re-deployed on apply (idempotent).

services:
my-stack:
swarm: prod
compose:
services:
web:
image: nginx:latest
ports:
- "80:80"
redis:
image: redis:alpine
env:
- KEY=value
volume-mappings:
- compose-name: data # Volume name inside compose definition
volume: data-vol # Manifest volume alias
FieldDescription
swarmSwarm alias to deploy to
composeInline Docker Compose content (services, networks, volumes, etc.)
envEnvironment variables passed to the compose stack
volume-mappingsMaps compose volume names to manifest volume aliases for NFS-backed storage

External Resources

Any node, volume, swarm, or Kubernetes cluster can be marked as external: true to reference an existing resource by name instead of creating it. External resources:

  • Must already exist in the API (looked up by the manifest alias as the resource name)
  • Are never created, modified, or deleted by apply or remove
  • Are never pruned by --prune
  • Can be referenced by other sections (e.g. compute instances can target an external swarm)
nodes:
web1:
external: true

volumes:
shared-data:
external: true

swarms:
prod:
external: true

kubernetes:
cluster1:
external: true
volumes: # Volumes and manifests can still be applied
shared-data: 1Gi
manifests:
- apiVersion: apps/v1
kind: Deployment
...

compute:
web-app:
swarm: prod # Uses the external swarm
image: nginx:latest
type: small

This is useful when:

  • Infrastructure is shared across multiple manifests (e.g. nodes managed by one manifest, apps by another)
  • You want to deploy workloads to existing swarms or clusters without managing their lifecycle
  • Teams are separated — infra team manages nodes/swarms, app team manages compute/services

external: true is mutually exclusive with resource-specific fields. For example, an external node cannot have ip or ssh fields, and an external swarm cannot have nodes or lb fields.

Variable Substitution

Any ${VAR} in the manifest is replaced before parsing. Variables are resolved in order:

  1. --env flags passed to the CLI
  2. OS environment variables
nodes:
web1:
ip: ${NODE1_IP}
ssh:
profile: prod-servers # SSH credentials from stored profile
nimbus manifest apply --file infra.yaml --env NODE1_IP=10.0.0.1

Variables can also be used with inline SSH credentials (without profiles):

nodes:
web1:
ip: ${NODE1_IP}
ssh:
user: ${SSH_USER}
password: ${NODE1_PASS}

Unresolved variables are left as-is (not treated as an error), so optional fields can reference variables that may not be set.

Idempotent Apply

Running nimbus manifest apply multiple times is safe:

  • Nodes: Skipped if the IP already exists and the node is ready
  • Volumes: Skipped if already active; errors if node or folder changed
  • Swarms/Kubernetes: Skipped if they exist; node membership is reconciled (adds missing, removes extra)
  • S3: Skipped if already exists
  • Compute: Skipped if unchanged; recreated if configuration drifted; scaled if only replicas changed
  • Services: Always re-deployed (compose stacks are inherently idempotent)

Pruning

When using --prune, DockNimbus tracks which resources belong to a manifest by tagging them with the manifest name. Resources that are tagged with the manifest name but no longer declared in the YAML are deleted in reverse dependency order:

services -> compute -> kubernetes -> s3 -> swarms -> volumes -> nodes

This is useful when you remove a section from your manifest and want the corresponding resources cleaned up automatically.

# First apply creates web-app and api-server
nimbus manifest apply --file infra.yaml --prune

# Later, remove api-server from the YAML and re-apply
# --prune will delete the orphaned api-server instance
nimbus manifest apply --file infra.yaml --prune

Removal

nimbus manifest remove tears down all resources declared in the manifest in reverse dependency order. Unlike --prune, it removes everything in the manifest, not just orphans.

nimbus manifest remove --file infra.yaml --env S3_PASSWORD=secret

Variable substitution flags are still needed on removal if the manifest contains variables (the file must parse successfully).

Full Example

See manifest-example.yaml for a complete working example.