Secrets & Binds¶
Binds are how you inject secrets and credentials into a capability spec. You declare what variables you need in the spec — the operator mounts the corresponding Kubernetes Secrets into the engine pod at the declared path.
How Binds Work¶
Capability spec declares: Operator expects: Engine reads:
────────────────────── ───────────────── ─────────────
binds: Secret /app/shared/
- namespace: "registry-env" {name}-bind-shared secrets.yaml
location: "file:///./shared/ (all keys combined)
secrets.yaml"
keys:
REGISTRY_TOKEN: "..."
- namespace: "dockyard-env"
location: "file:///./shared/
secrets.yaml"
keys:
DOCKYARD_API_KEY: "..."
The engine resolves {{REGISTRY_TOKEN}} and {{DOCKYARD_API_KEY}} at
runtime by reading the mounted file.
Secret Naming Convention¶
The operator derives the Secret name from the parent directory of the
bind location path — not from the bind namespace:
location: "file:///./shared/secrets.yaml"
→ parent dir: "shared"
→ Secret name: {capability-name}-bind-shared
location: "file:///./config/db.yaml"
→ parent dir: "config"
→ Secret name: {capability-name}-bind-config
Why parent-dir, not namespace? Multiple bind namespaces often point to the same file path. If the Secret were named per-namespace, two Secrets with the same filename key (
secrets.yaml) could not be mounted into the same directory — Kubernetes projected volumes would silently overwrite one with the other. One Secret per file path eliminates this conflict entirely.
Creating Bind Secrets¶
All bind namespaces that share the same location path must have their keys
combined into one Secret:
# Two bind namespaces, same location → one Secret with all keys
kubectl create secret generic my-capability-bind-shared \
--from-literal=secrets.yaml=$'registry-bearer-token: "abc123"\nregistry-api-version: "1.0"\nmcp-server-token: "sk-mcp-xxx"\ndockyard-api-key: "dock-key"' \
-n default
The Secret key (secrets.yaml) must match the filename in the location path.
Multiple Bind Locations¶
If your binds point to different file paths, create one Secret per path:
binds:
- namespace: "registry-env"
location: "file:///./shared/secrets.yaml" # → {name}-bind-shared
keys:
REGISTRY_TOKEN: "registry-bearer-token"
- namespace: "db-env"
location: "file:///./config/db.yaml" # → {name}-bind-config
keys:
DB_PASSWORD: "database-password"
kubectl create secret generic my-capability-bind-shared \
--from-literal=secrets.yaml='registry-bearer-token: "abc123"' \
-n default
kubectl create secret generic my-capability-bind-config \
--from-literal=db.yaml='database-password: "secret"' \
-n default
Bind Spec Fields¶
binds:
- namespace: "registry-env" # logical name used in spec variables
description: "Registry credentials"
location: "file:///./shared/secrets.yaml" # file:// path inside the pod
keys:
REGISTRY_TOKEN: "registry-bearer-token" # variable → key in the Secret
REGISTRY_VERSION: "registry-api-version"
MCP_SERVER_TOKEN: "mcp-server-token"
| Field | Description |
|---|---|
namespace |
Logical name — used to scope {{VARIABLE}} references |
description |
Human-readable description |
location |
file:// URI of the secrets file inside the pod |
keys |
Map of variable name → key name in the Secret |
Using Bind Variables in the Spec¶
Reference bind variables anywhere in the spec using {{VARIABLE_NAME}}:
capability:
consumes:
- namespace: registry
type: http
baseUri: "https://api.example.com"
authentication:
type: bearer
token: "{{REGISTRY_TOKEN}}" # injected from bind
exposes:
- type: mcp
port: 3001
namespace: shipyard-tools
authentication:
type: bearer
token: "{{MCP_SERVER_TOKEN}}" # injected from bind
What the Operator Creates¶
For each unique bind location, the operator:
- Looks up the Secret
{capability-name}-bind-{parent-dir}in the namespace - Mounts it as a volume at the parent directory (e.g.
/app/shared/) - The Secret key (
secrets.yaml) becomes a file at/app/shared/secrets.yaml
# Check the volume mounts on the generated Deployment
kubectl get deployment my-capability -n default \
-o jsonpath='{.spec.template.spec.containers[0].volumeMounts}' \
| python3 -m json.tool
Secret Must Exist Before the CR¶
The operator throws a ReconcileError if the Secret does not exist:
Bind Secret 'my-capability-bind-shared' not found in namespace 'default'.
Create it before applying the Capability CR.
kubectl create secret generic my-capability-bind-shared \
--from-file=secrets.yaml=<path> -n default
Always create your Secrets before applying the Capability CR.
Complete Example¶
# ikanos spec (in ConfigMap)
ikanos: "1.0.0-alpha3"
binds:
- namespace: "registry-env"
location: "file:///./shared/secrets.yaml"
keys:
REGISTRY_TOKEN: "registry-bearer-token"
REGISTRY_VERSION: "registry-api-version"
MCP_SERVER_TOKEN: "mcp-server-token"
- namespace: "dockyard-env"
location: "file:///./shared/secrets.yaml" # same path → same Secret
keys:
DOCKYARD_API_KEY: "dockyard-api-key"