CLI Reference¶
bindy is a single binary with multiple subcommands, each running as an independent controller loop.
bindy <SUBCOMMAND> [OPTIONS]
Subcommands:
bootstrap Bootstrap bindy components into the cluster
run Run the main BIND9 DNS operator (all controllers)
scout Run the Scout controller (Ingress and Service → ARecord)
completion Output shell completion code for the specified shell
version Print the binary version (same output as --version)
Options:
-V, --version Print the binary version (matches the crate version, e.g. `bindy 0.6.0`)
bindy bootstrap¶
Applies Kubernetes resources via server-side apply. Every operation is idempotent — safe to run multiple times and safe to use in automation pipelines.
bindy bootstrap <SUBCOMMAND> [OPTIONS]
Subcommands:
operator Apply namespace, CRDs, RBAC, and the operator Deployment
scout Apply namespace, CRDs, scout RBAC, and the scout Deployment
multi-cluster (alias: mc) Create a child cluster's ServiceAccount on the bindy
cluster, optionally register it for its cluster names and zones,
and write its kubeconfig Secret to a private file
bindy bootstrap operator¶
Applies the following resources in order:
Namespace(bindy-systemby default)- All 12 CRDs (
bindy.firestoned.io/v1beta1) ServiceAccount/bindyClusterRole/bindy-role— operator permissionsClusterRole/bindy-admin-role— admin/destructive permissionsClusterRoleBinding/bindy-rolebindingDeployment/bindy— the operator itself
Options¶
| Flag | Default | Description |
|---|---|---|
--namespace <NS> |
bindy-system |
Namespace to install the operator into. |
--version <TAG> |
Binary version (e.g. v0.5.0) |
Image tag for the operator Deployment. Use latest for debug builds. |
--registry <REGISTRY> |
— | Override the container registry. See Air-gapped environments. |
--dry-run |
— | Print all resources as YAML without connecting to the cluster. |
Examples¶
# Standard install — image tag matches the binary version automatically
bindy bootstrap operator
# Pin to a specific version
bindy bootstrap operator --version v0.5.0
# Custom namespace
bindy bootstrap operator --namespace my-bindy
# Preview what would be applied without touching the cluster
bindy bootstrap operator --dry-run
# Air-gapped: pull from a private registry mirror
bindy bootstrap operator --registry harbor.corp.internal/bindy-mirror
# Air-gapped with explicit version
bindy bootstrap operator --registry harbor.corp.internal/bindy-mirror --version v0.5.0
bindy bootstrap scout¶
Applies the following resources in order:
Namespace(bindy-systemby default)- All 12 CRDs — same set as the operator, so this is safe to run on a cluster that already has the operator installed
ServiceAccount/bindy-scoutClusterRole/bindy-scout— cluster-wide Ingress watch and Secret read (for remote kubeconfig); DNSZone read is cluster-wide in same-cluster mode onlyClusterRoleBinding/bindy-scoutRole/bindy-scout-writer—ARecordwrite access in the target namespaceRoleBinding/bindy-scout-writer
Multi-cluster mode
In multi-cluster mode, Scout validates DNSZones via the remote kubeconfig using a namespaced Role on the Queen Bee cluster (created by bindy bootstrap mc). No cluster-wide DNSZone access is required on the Queen Bee cluster.
Deployment/bindy-scout— the scout controller
Options¶
| Flag | Default | Description |
|---|---|---|
--namespace <NS> |
bindy-system |
Namespace to install Scout into. Scout also writes its ARecords there (BINDY_SCOUT_NAMESPACE). |
--version <TAG> |
Binary version (e.g. v0.5.0) |
Image tag for the Scout Deployment. |
--registry <REGISTRY> |
unset | Override the container registry. See Air-gapped environments. |
--dry-run |
off | Print all resources as YAML without connecting to the cluster. |
--cluster-name <NAME> |
default |
Logical name of this cluster, passed to Scout as --cluster-name and stamped on every ARecord as bindy.firestoned.io/source-cluster. Use a unique name per cluster. |
--default-ips <IP[,IP]> |
unset | Passed to Scout as --default-ips. |
--default-zone <ZONE> |
unset | Passed to Scout as --default-zone. |
--remote-secret <NAME> |
unset | Name of the kubeconfig Secret from bindy bootstrap mc; sets BINDY_SCOUT_REMOTE_SECRET so Scout writes to the bindy cluster. |
--allowed-zones <ZONE[,ZONE]> |
unset | Passed to Scout as --allowed-zones: the zones this cluster may publish into, each with its subzones. Recommended (ADR-0025). |
--allowed-ip-ranges <CIDR[,CIDR]> |
unset | Passed to Scout as --allowed-ip-ranges: the IPv4 ranges every published address must fall in. Recommended. |
The allowlist values are checked by Scout when it starts, not by bootstrap:
a malformed entry, or a --default-zone / --default-ips outside them,
makes the Scout pod exit with an error naming the setting.
Examples¶
# Standard install
bindy bootstrap scout
# Preview resources without applying
bindy bootstrap scout --dry-run
# Air-gapped: pull from a private registry mirror
bindy bootstrap scout --registry harbor.corp.internal/bindy-mirror
# A child cluster writing to a bindy cluster, limited to its own zone and
# load-balancer range (the Secret comes from `bindy bootstrap mc`)
bindy bootstrap scout \
--cluster-name cluster-a \
--remote-secret bindy-scout-cluster-a-kubeconfig \
--allowed-zones cluster-a.example.com \
--allowed-ip-ranges 10.20.0.0/24
CRDs are always included
Both bootstrap operator and bootstrap scout apply the full set of CRDs. Running both commands on the same cluster is safe — the CRDs are applied via server-side apply and remain unchanged on subsequent runs.
bindy bootstrap multi-cluster (alias mc)¶
Run against the bindy (Queen Bee) cluster, once per child cluster. It creates the ServiceAccount a child cluster's Scout writes with, and writes that account's kubeconfig as a Secret manifest to a private file for you to apply on the child cluster.
Applies, in --namespace (all named after --service-account):
ServiceAccount/<sa>Role/<sa>:arecordsget, list, watch, create, update, patch, delete;dnszonesget, list, watchRoleBinding/<sa>- With
--cluster-nameand--allowed-zones: the keys<sa>.clustersand<sa>.zonesinConfigMap/bindy-scout-sources, under the field managerbindy-bootstrap-mc.<sa>(other accounts' keys are left alone) Secret/<sa>-token(akubernetes.io/service-account-tokenSecret), then waits for the token
Then writes Secret/<sa>-kubeconfig (type bindy.firestoned.io/remote-kubeconfig)
to --output, mode 0600. It is never printed to stdout.
Options¶
| Flag | Default | Description |
|---|---|---|
--namespace <NS> |
bindy-system |
Namespace on the bindy cluster for the account, and where its Scout writes ARecords. |
--service-account <NAME> |
bindy-scout-remote |
Account to create. Use one per child cluster, so each can be revoked and registered on its own. |
--cluster-name <NAME[,NAME]> |
unset | Cluster name(s) this account's Scout may stamp in source-cluster (its own --cluster-name). Requires --allowed-zones. List a second name while renaming a cluster. |
--allowed-zones <ZONE[,ZONE]> |
unset | Zones this account may publish into, each with its subzones. Requires --cluster-name. Lowercased, trailing dot dropped. |
--server <URL> |
the KUBECONFIG server | API server URL written into the kubeconfig, when the child cluster reaches the bindy cluster at another address. |
-o, --output <PATH> |
<sa>-kubeconfig.yaml |
Where to write the Secret manifest (mode 0600; - is refused). |
--insecure-skip-tls-verify |
off | Allow a kubeconfig without a CA bundle. Local testing only. |
--revoke (alias --delete) |
off | Delete the account's RoleBinding, Role, Secrets and ServiceAccount, and remove its keys from bindy-scout-sources. Safe to repeat. |
With --cluster-name and --allowed-zones the account is registered:
the bindy-scout-source-binding admission policy then refuses any ARecord
create, update or delete it makes outside those cluster names and zones (see
Binding each cluster to its names and zones).
Without them the command warns that the account is unconstrained.
Examples¶
# A registered account for child cluster cluster-a
bindy bootstrap mc \
--service-account bindy-scout-cluster-a \
--cluster-name cluster-a \
--allowed-zones cluster-a.example.com \
-o cluster-a.yaml
kubectl --context=cluster-a apply -f cluster-a.yaml && rm cluster-a.yaml
# Register an existing account without changing anything else. It re-applies
# the same ServiceAccount and RBAC and writes the kubeconfig file again (same
# token); the child cluster's Secret does not need re-applying.
bindy bootstrap mc --service-account bindy-scout-cluster-a \
--cluster-name cluster-a --allowed-zones cluster-a.example.com -o register.yaml
rm register.yaml
# Renaming cluster-a to cluster-a2: allow both names during the switch
bindy bootstrap mc --service-account bindy-scout-cluster-a \
--cluster-name cluster-a2,cluster-a --allowed-zones cluster-a.example.com -o register.yaml
rm register.yaml
# Remove the account and its registration
bindy bootstrap mc --revoke --service-account bindy-scout-cluster-a
Air-gapped environments¶
In environments without internet access, images must be mirrored to a private registry. Use --registry to point both subcommands at the mirror.
The --registry value replaces the default ghcr.io/firestoned prefix. The image name (bindy) and tag (--version) are kept:
--registry |
--version |
Resulting image |
|---|---|---|
| (not set) | v0.5.0 |
ghcr.io/firestoned/bindy:v0.5.0 |
harbor.corp.internal/bindy-mirror |
v0.5.0 |
harbor.corp.internal/bindy-mirror/bindy:v0.5.0 |
registry.example.com |
latest |
registry.example.com/bindy:latest |
Typical air-gapped workflow:
# 1. On an internet-connected machine, pull and re-tag the image
docker pull ghcr.io/firestoned/bindy:v0.5.0
docker tag ghcr.io/firestoned/bindy:v0.5.0 harbor.corp.internal/bindy-mirror/bindy:v0.5.0
docker push harbor.corp.internal/bindy-mirror/bindy:v0.5.0
# 2. Copy the bindy binary to the air-gapped environment
# (scp, USB, artifact repository, etc.)
# 3. Bootstrap using the private registry
bindy bootstrap operator --registry harbor.corp.internal/bindy-mirror --version v0.5.0
bindy bootstrap scout --registry harbor.corp.internal/bindy-mirror --version v0.5.0
Use --dry-run first to verify the image reference before applying:
bindy bootstrap operator --dry-run --registry harbor.corp.internal/bindy-mirror --version v0.5.0 \
| grep "image:"
bindy run¶
Starts the main operator. Manages the full lifecycle of all BIND9 DNS custom resources:
ClusterBind9Provider, Bind9Cluster, Bind9Instance, DNSZone, and all DNS record types.
All configuration is via environment variables. See Environment Variables for the full reference.
What it starts¶
| Controller | Watches | Manages |
|---|---|---|
ClusterBind9Provider |
ClusterBind9Provider | Bind9Cluster |
Bind9Cluster |
Bind9Cluster | Bind9Instance |
Bind9Instance |
Bind9Instance, Deployment, Service, … | DNS server pods |
DNSZone |
DNSZone, all record types, Bind9Instance | Zone files |
ARecord |
ARecord | A record in BIND9 |
AAAARecord |
AAAARecord | AAAA record in BIND9 |
CNAMERecord |
CNAMERecord | CNAME record in BIND9 |
MXRecord |
MXRecord | MX record in BIND9 |
NSRecord |
NSRecord | NS record in BIND9 |
TXTRecord |
TXTRecord | TXT record in BIND9 |
SRVRecord |
SRVRecord | SRV record in BIND9 |
CAARecord |
CAARecord | CAA record in BIND9 |
PTRRecord |
PTRRecord | PTR record in BIND9 |
Environment variables¶
Kubernetes client¶
| Variable | Default | Description |
|---|---|---|
BINDY_KUBE_QPS |
20.0 |
Sustained API server request rate (queries per second), enforced client-side |
BINDY_KUBE_BURST |
30 |
Requests allowed to burst above the sustained rate |
BINDY_KUBE_REQUEST_TIMEOUT_SECS |
30 |
Deadline in seconds for each non-watch API request (watches exempt); a timed-out request is retried with backoff |
Leader election¶
| Variable | Default | Description |
|---|---|---|
BINDY_ENABLE_LEADER_ELECTION |
true |
Set to false to disable. Only disable for local development. |
BINDY_LEASE_NAME |
bindy-leader |
Name of the Kubernetes Lease object used for leader election. |
BINDY_LEASE_NAMESPACE |
$POD_NAMESPACE or bindy-system |
Namespace where the Lease object lives. |
BINDY_LEASE_DURATION_SECONDS |
15 |
Lease duration. A new leader cannot be elected until this expires. |
BINDY_LEASE_RENEW_DEADLINE_SECONDS |
10 |
Time within which the leader must renew its lease. |
BINDY_LEASE_RETRY_PERIOD_SECONDS |
2 |
How often non-leaders attempt to acquire the lease. |
POD_NAME |
— | Leader election identity. Falls back to $HOSTNAME, then a random string. Set via Kubernetes downward API. |
POD_NAMESPACE |
— | Pod namespace. Injected by Kubernetes downward API. Used for lease namespace fallback. |
Metrics¶
| Variable | Default | Description |
|---|---|---|
BINDY_METRICS_BIND_ADDRESS |
0.0.0.0 |
Prometheus scrape endpoint bind address. |
BINDY_METRICS_PORT |
8080 |
Prometheus scrape endpoint port. |
BINDY_METRICS_PATH |
/metrics |
HTTP path for Prometheus scraping. |
Logging¶
| Variable | Default | Description |
|---|---|---|
RUST_LOG |
info |
Log level filter. Values: trace, debug, info, warn, error. Supports module-level filters, e.g. bindy=debug,kube=warn. |
RUST_LOG_FORMAT |
text |
Log output format. text = compact human-readable; json = structured for log aggregators (Loki, ELK, Splunk). |
bindy scout¶
Starts the Scout controller. Watches Ingress, LoadBalancer Service, and Gateway API HTTPRoute / GRPCRoute / TLSRoute / TCPRoute resources cluster-wide and publishes one ARecord per DNS name for opted-in resources. See Bindy Scout for the full conceptual guide.
Options¶
| Flag | Env var | Default | Description |
|---|---|---|---|
--cluster-name <NAME> |
BINDY_SCOUT_CLUSTER_NAME |
none | Required. Logical name of this cluster, stamped on all created ARecord labels as bindy.firestoned.io/source-cluster. Used to distinguish records from multiple workload clusters writing to the same bindy namespace. |
--namespace <NS> |
BINDY_SCOUT_NAMESPACE |
bindy-system |
Namespace where ARecord CRs are created. |
--default-zone <ZONE> |
BINDY_SCOUT_DEFAULT_ZONE |
unset | Zone used when a source has no bindy.firestoned.io/zone annotation. Must be inside --allowed-zones when that is set. |
--default-ips <IP[,IP]> |
BINDY_SCOUT_DEFAULT_IPS |
unset | Addresses used when a source has no IP annotation and none is discovered. Must be inside --allowed-ip-ranges when that is set. |
--gateway-service <class=target> |
BINDY_SCOUT_GATEWAY_SERVICES |
unset | Repeatable gatewayClass to LoadBalancer Service(s) map for route IP resolution: class=namespace/name (one Service) or class=namespace/<label-selector> (every matching LoadBalancer Service, scoped to the route's Gateway by the GEP-1762 gateway.networking.k8s.io/gateway-name label when present; equality-based selectors only). ADR-0028. |
--namespace-selector <SELECTOR> |
BINDY_SCOUT_NAMESPACE_SELECTOR |
unset (every namespace) | Label selector a source's namespace must match. Recommended. |
--allowed-zones <ZONE[,ZONE]> |
BINDY_SCOUT_ALLOWED_ZONES |
unset (every zone) | Zones Scout may publish into, each with its subzones. A source naming another zone publishes nothing. * is refused. Recommended (ADR-0025). |
--allowed-ip-ranges <CIDR[,CIDR]> |
BINDY_SCOUT_ALLOWED_IP_RANGES |
unset (every address) | IPv4 ranges (a bare address means /32) every published address must fall in. A source with any address outside publishes nothing, except a route's addresses discovered from its Gateway, which are dropped one by one with a warning (ADR-0028). Recommended. |
--remote-endpoint <URL> |
BINDY_SCOUT_REMOTE_ENDPOINT |
unset | Bindy cluster API URL for the endpoint remote mode (ADR-0008). Requires --remote-token-file. |
--remote-token-file <PATH> |
BINDY_SCOUT_REMOTE_TOKEN_FILE |
unset | Bearer-token file minted by the bindy cluster. |
--remote-ca-file <PATH> |
BINDY_SCOUT_REMOTE_CA_FILE |
unset (webpki roots) | CA bundle for the remote endpoint. |
CLI takes precedence
When both a CLI flag and the corresponding environment variable are set, the CLI flag wins.
Additional environment variables¶
| Variable | Default | Description |
|---|---|---|
POD_NAMESPACE |
default |
Scout's own namespace. Always excluded from Ingress watching. Set via Kubernetes downward API. |
BINDY_SCOUT_EXCLUDE_NAMESPACES |
— | Comma-separated list of namespaces to skip in addition to POD_NAMESPACE. |
BINDY_SCOUT_REMOTE_SECRET |
unset | Name of the kubeconfig Secret (from bindy bootstrap mc) for the kubeconfig remote mode. |
BINDY_SCOUT_REMOTE_SECRET_NAMESPACE |
POD_NAMESPACE |
Namespace of that Secret. |
RUST_LOG |
info |
Log level filter. |
RUST_LOG_FORMAT |
text |
Log format (text or json). |
Examples¶
# Minimal — cluster name required, everything else defaults
bindy scout --cluster-name prod
# Explicit namespace
bindy scout --cluster-name prod --namespace bindy-system
# Using environment variables only (typical Kubernetes deployment)
BINDY_SCOUT_CLUSTER_NAME=prod BINDY_SCOUT_NAMESPACE=bindy-system bindy scout
# Mix: cluster name from CLI flag, namespace from environment
BINDY_SCOUT_NAMESPACE=bindy-system bindy scout --cluster-name staging
# Debug logging
RUST_LOG=bindy=debug bindy scout --cluster-name dev
# Production: bounded to this cluster's zone and load-balancer range
bindy scout --cluster-name prod \
--allowed-zones prod.example.com \
--allowed-ip-ranges 10.20.0.0/24
Scout validates its whole configuration at startup and exits with an error
naming the setting when a value is empty, malformed, or (for
--default-zone and --default-ips) outside the allowlists. With neither
allowlist set it starts, and logs a warning for each.
Ingress annotations¶
Scout reads the following annotations from every source kind (see the Annotations Reference for the full rules):
| Annotation | Required | Description |
|---|---|---|
bindy.firestoned.io/scout-enabled |
Yes ("true") |
Opts the resource in. The legacy bindy.firestoned.io/recordKind: "ARecord" is still accepted on Ingresses. |
bindy.firestoned.io/zone |
Yes, unless --default-zone is set |
DNS zone that owns the hosts (e.g. example.com). Must be inside --allowed-zones when that is set. |
bindy.firestoned.io/ip |
No | Explicit IP override. Defaults to the discovered address. Must be inside --allowed-ip-ranges when that is set. |
bindy.firestoned.io/ttl |
No | TTL override in seconds. |
bindy.firestoned.io/record-name |
No | Record name override (@ for the apex). Required on TCPRoute. |
bindy completion¶
Outputs shell completion code for the specified shell. No environment variables or Kubernetes access required.
Supported shells¶
| Shell | Value |
|---|---|
| Bash | bash |
| Zsh | zsh |
| Fish | fish |
| PowerShell | powershell |
Installation¶
Add to ~/.bashrc:
Or for a system-wide install:
Add to ~/.zshrc:
Or with oh-my-zsh:
Or to persist across sessions: