Kinds do manifesto
Cada Kind com um template COMPLETO e funcional — todos os campos, com os defaults e um comentário. Aplica um só com delonix <grupo> apply -f, ou todos de uma vez com delonix stack apply (ordem por dependência: Secret → Network → NetworkRoute → Volume → Image → VirtualMachine → Container → Pod → Ingress → NetworkPolicy → HTTPRoute → Gateway; Storage e ShareVolume baixam para Volume antes desta ordem entrar em jogo). A lista completa, com os grupos e o que cada documento se torna, está em Estrutura de recursos.
Each Kind with a COMPLETE, functional template — every field, with defaults and a comment. Apply just one with delonix <group> apply -f, or all at once with delonix stack apply (dependency order: Secret → Network → NetworkRoute → Volume → Image → Vm → Container → Pod → Ingress → FirewallPolicy → HTTPRoute → Tunnel — ShareVolume lowers into Volume before this order comes into play).
Desde a v0.47.0 o apply converge: o stack plan diz o que mudaria e porquê, e o apply reconfigura portas, volumes, redes, memória e CPU a quente, sem mudar o PID — recusando, com o nome do campo, o que obrigaria a recriar (a não ser com --replace). Sem ficheiro de estado: o último spec aplicado vive no próprio recurso. Continua fail-fast e sem rollback, e é convergência a pedido — não um loop. Os templates abaixo são os ficheiros reais em examples/.
Since v0.47.0 apply converges: stack plan says what would change and why, and apply reconfigures ports, volumes, networks, memory and CPU hot, with the PID unchanged — refusing, by field name, anything that would need a recreate (unless you pass --replace). No state file: the last applied spec lives on the resource itself. Still fail-fast and without rollback, and still convergence on demand — not a loop. The templates below are the real files in examples/.
Secret
Um segredo do cofre cifrado em repouso. Consumido por run --secret/--secret-files e por passwordSecret do Storage. Os valores NUNCA ficam no registo do container em texto — são resolvidos no arranque a partir do NOME.
A secret from the vault, encrypted at rest. Consumed by run --secret/--secret-files and by Storage's passwordSecret. Values are NEVER kept in the container's registry as plaintext — they're resolved at startup from the NAME.
# Secret — um saco de pares CHAVE=valor, cifrado em repouso (XChaCha20-Poly1305
# sob a chave-mestra do host). Consumido por `Container.secret` (env ou
# /run/secrets/<nome>) e por `Storage.passwordSecret` (lê a chave `password`).
#
# É a forma DECLARATIVA do `delonix secret create` — assim o ciclo fecha-se só
# em YAML, sem CLI. Apply: delonix secret apply -f examples/secret.yaml
# (ou, junto com o resto, `delonix stack apply` — os Secrets são aplicados
# ANTES do Storage/Container que os referenciam.)
# ── Inline (stringData) — cómodo para dev, mas os VALORES ficam em CLARO no
# ficheiro. NÃO commites um manifesto destes num repositório. ───────────────
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: nas-creds
spec:
stringData:
password: s3cr3t # Storage.passwordSecret lê exactamente esta chave
---
# ── fromEnvFile — mantém os valores FORA do manifesto (carrega linhas
# KEY=value de um ficheiro .env, tipicamente fora do controlo de versões). ──
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: app-env
spec:
fromEnvFile: ./app.env # ex.: DATABASE_URL=..., API_TOKEN=...
Todas as possibilidades — examples/full-secret.yamlEvery option — examples/full-secret.yaml
# Complete reference for `kind: Secret` — every field of `spec` and every source form.
# A Secret is a bag of KEY=value pairs, encrypted at rest. It is consumed by
# `Container.secret` (env or /run/secrets files), `Volume.<block>.passwordSecret`,
# `Image.pullSecret` and `provision.truenas.*Secret`.
#
# The three sources (stringData, fromEnvFile, fromEnv) can be combined in ONE document;
# they are shown separately below for clarity. Precedence when combined:
# fromEnvFile first, then stringData overrides it (inline wins over the file).
#
# delonix stack apply -f examples/full-secret.yaml --dry-run
#
# NOTE: there is no `data:` (base64) field — the engine only reads the three below.
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: inline-secret
labels: # free-form labels stored with the resource
app: demo
annotations: # free-form annotations
delonix.io/purpose: "example only"
spec:
# stringData: inline KEY: value pairs. Values are CLEARTEXT in the manifest, so the
# apply prints a warning — fine for dev, never commit real values.
stringData:
password: change-me # the key `passwordSecret` fields look for
apiKey: dummy-api-key # `apiKeySecret` fields look for `apiKey` (or `token`)
username: demo # `pullSecret` wants `username` + `password`
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: file-secret
spec:
# fromEnvFile: load `KEY=value` lines from a .env-style file, keeping the values OUT of
# the manifest. A relative path is resolved against the manifest's own folder.
fromEnvFile: ./secret-source.env
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: env-map-secret
spec:
# fromEnv, mapping form: `<key in the secret>: <environment variable to read>`.
# The key is what the consumer looks up; the variable is whatever the CI job exports.
# An unset variable is an ERROR at apply time, never an empty secret.
fromEnv:
password: EXAMPLE_DB_PASSWORD
token: EXAMPLE_API_TOKEN
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: env-list-secret
spec:
# fromEnv, list form: shorthand when the key name and the variable name are the same.
fromEnv:
- DATABASE_URL
- SENTRY_DSN
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: combined-secret
spec:
# All three sources at once: the file gives the base, stringData overrides `LOG_LEVEL`,
# fromEnv adds a value read from the process environment.
fromEnvFile: ./secret-source.env
stringData:
LOG_LEVEL: debug
fromEnv:
- EXAMPLE_EXTRAPod
O schema de Pod do Kubernetes (spec.containers[]) — portas/env/resources/securityContext/volumeMounts estruturados. O MESMO schema continua aceite num kind: Container, com aviso, e não é reescrito de propósito: um kind: Container chamado web cria um container web, este cria a netns pod-web cujo membro é web-c0 — renomear um workload nas costas de quem o escreveu partiria o DNS e os backends de HTTPRoute.
The Kubernetes Pod schema (spec.containers[]) — structured ports/env/resources/securityContext/volumeMounts. The SAME schema is still accepted on a kind: Container, with a warning, and is deliberately not rewritten for you: a kind: Container named web creates a container web, this creates the pod netns pod-web whose member is web-c0 — renaming a workload behind your back would break DNS and HTTPRoute backends.
# kind: Pod — the familiar k8s schema: spec.containers[] with structured
# ports/env/resources/securityContext + pod-level volumes. Apply with:
# delonix stack apply -f examples/pod.yaml
#
# The SAME schema is still accepted on a `kind: Container`, but with a
# deprecation warning, and it is deliberately not rewritten for you: a
# `kind: Container` named `web` creates a container called `web`, while this
# creates the pod netns `pod-web` whose member is `web-c0`. Renaming a workload
# behind your back would break DNS, HTTPRoute backends and cross-references.
#
# For a pod with SEVERAL containers sharing the namespaces, see pod-multi.yaml.
# The flat single-container spec (see examples/*, `image:` at the top) still works.
# ---------------------------------------------------------------------------
# A rede que o pod usa, declarada aqui para o exemplo validar sozinho.
# ---------------------------------------------------------------------------
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: web-net
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: web
namespace: default
spec:
# delonix extensions (pod-level):
network: web-net # host | none | <custom network>
restartPolicy: Always # Always | OnFailure | Never (delonix values also OK)
expose: 80 # auto-register this HTTP port in the L7 proxy (optional)
hostAliases: # extra /etc/hosts entries, k8s shape (one entry per hostname)
- ip: 10.0.0.9
hostnames: [legacy-api, legacy-api.internal]
containers:
- name: web
image: nginx:latest
command: ["/bin/sh", "-c"] # overrides ENTRYPOINT (k8s semantics)
args: ["nginx -g 'daemon off;'"] # overrides CMD
ports:
- containerPort: 80
hostPort: 8080 # published only when hostPort is set
protocol: TCP
env:
- name: TZ
value: UTC
volumeMounts:
- name: site
mountPath: /usr/share/nginx/html
readOnly: true
- name: scratch
mountPath: /tmp/cache
resources:
limits:
cpu: "500m" # 500m → 0.5 cores
memory: 256Mi
securityContext:
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
volumes:
- name: site
hostPath:
path: /srv/www # bind mount
- name: scratch
emptyDir: {} # ephemeral → tmpfs
Todas as possibilidades — examples/full-pod.yamlEvery option — examples/full-pod.yaml
# kind: Pod — a real multi-container pod: N containers share ONE network namespace
# (same IP, `localhost` between them), plus IPC/UTS (and optionally PID) sharing.
# The schema is the k8s Pod one (`spec.containers[]`, structured ports/env/resources/
# securityContext, pod-level `volumes[]`) plus an engine-specific `network` field.
#
# delonix stack apply -f examples/full-pod.yaml --dry-run
# delonix stack validate -f examples/full-pod.yaml
#
# Documents in this file:
# 1. supporting resources (Network, Volumes) referenced by the pods
# 2. `full-pod` — every pod-level field and every container field
# 3. `shared-pid` — shareProcessNamespace: true
# 4. `minimal-pod` — the smallest valid pod
# ---------------------------------------------------------------------------
# 1. Supporting resources
# ---------------------------------------------------------------------------
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: pod-net
spec:
driver: bridge
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: pod-data # referenced through `persistentVolumeClaim.claimName`
spec:
driver: local
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: pod-cache # referenced through `source`
spec:
driver: local
---
# ---------------------------------------------------------------------------
# 2. Every field
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: full-pod
namespace: default # isolation namespace (a pod is isolated like a container)
labels:
app: full-pod
spec:
# ---- pod-level ----
network: pod-net # delonix extension: SDN network of the pod's shared netns
# (host | none | <unset> = the default bridge; a custom name selects that network)
restartPolicy: OnFailure # Always | OnFailure | Never (the engine spellings always/on-failure/no are also accepted)
hostname: full-pod-host # hostname shared by every member (default: the pod name)
shareProcessNamespace: false # true => members see each other's processes (see `shared-pid` below)
hostAliases: # extra /etc/hosts entries in every member, k8s shape
- ip: 10.0.0.9
hostnames: [legacy-api, legacy-api.internal]
- ip: 10.0.0.10
hostnames: [billing]
# ---- pod-level volumes, referenced by containers[].volumeMounts[].name ----
volumes:
- name: site
hostPath: # bind mount of a host directory
path: /srv/www
- name: scratch
emptyDir: # ephemeral, backed by tmpfs (host RAM) in this engine
medium: Memory # "" would warn: k8s means node disk there, this engine always uses tmpfs
- name: data
persistentVolumeClaim: # a NAMED volume (a `kind: Volume`), by name
claimName: pod-data
- name: cache
source: pod-cache # delonix extension: a named Volume by plain source string
# ---- members ----
containers:
- name: web # member name; the container is called <pod>-<name> (default c<index>)
image: nginx:latest # REQUIRED
command: ["/bin/sh", "-c"] # k8s `command` => overrides the image ENTRYPOINT
args: ["nginx -g 'daemon off;'"] # k8s `args` => overrides the image CMD
workingDir: /usr/share/nginx/html # directory the process starts in
ports:
- containerPort: 80
hostPort: 8080 # published on the host ONLY when hostPort is set
protocol: TCP # TCP | UDP (default TCP)
hostIP: 127.0.0.1 # bind address of the published port
- containerPort: 443 # no hostPort => informational only, not published
env:
- name: TZ
value: Africa/Luanda
- name: LOG_LEVEL
value: info
volumeMounts:
- name: site
mountPath: /usr/share/nginx/html
readOnly: true
- name: scratch
mountPath: /tmp/cache
- name: data
mountPath: /var/lib/web
- name: cache
mountPath: /var/cache/web
resources:
limits: # enforced as cgroup limits (`requests` are advisory here and not set)
cpu: "500m" # 500m => 0.5 cores
memory: 256Mi
securityContext:
privileged: false
runAsUser: 101
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
- name: sidecar # a second member: reaches `web` on localhost (shared netns)
image: busybox:latest
command: ["sh", "-c"]
args: ["while true; do wget -qO- http://localhost:80/ >/dev/null 2>&1; sleep 10; done"]
resources:
limits:
cpu: "100m"
memory: 64Mi
---
# ---------------------------------------------------------------------------
# 3. Shared PID namespace: the members see each other's processes
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: shared-pid
spec:
shareProcessNamespace: true
containers:
- name: app
image: busybox:latest
command: ["sleep", "infinity"]
- name: debug
image: busybox:latest
command: ["sleep", "infinity"]
---
# ---------------------------------------------------------------------------
# 4. Minimal: only `containers[].image` is mandatory
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: minimal-pod
spec:
containers:
- image: alpine:3.20 # unnamed member => container "minimal-pod-c0"
command: ["sleep", "infinity"]
# Not set above, and why:
# spec.detach, spec.expose are accepted by the schema but a `kind: Pod` ignores them (members are always
# started detached and are not auto-registered in the L7 proxy) — use a
# HTTPRoute/Ingress towards the pod for L7 exposure.
# containers[].tty accepted and ignored (the engine decides tty by attach/detach).
# resources.requests advisory only; the engine enforces `limits`.Workload
UM objecto declarativo para os dois tipos de computação: spec.type: container | vm | pod | microvm + o bloco com o mesmo nome. Baixa para o Kind correspondente no load — não redefine um único campo, por isso não pode divergir dele.
ONE declarative object for both kinds of compute: spec.type: container | vm | pod | microvm plus the block with that same name. Lowers to the matching Kind on load — it doesn't redefine a single field, so it can never drift from it.
# Workload — one declarative object for BOTH compute types (ADR-0001).
#
# delonix stack apply -f examples/workload.yaml
# delonix stack apply --dry-run -f examples/workload.yaml # see what it lowers to
#
# A `kind: Workload` is sugar: it does NOT survive the load — it is rewritten into
# a `kind: Container` or `kind: VirtualMachine` and flows through the normal per-Kind apply,
# exactly like a `kind: Stack` child. `spec.type` picks the type; the block named
# after the type (`container:` / `vm:`) is the SAME spec the standalone Kind takes
# (see examples/container.yaml and examples/vm.yaml for every field).
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: web
namespace: default
spec:
type: container # container | vm (pod / microvm are reserved — a clear error, not silent)
container: # == kind: Container spec (examples/container.yaml)
image: nginx:alpine
restartPolicy: always
env:
- TZ=Africa/Luanda
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: db
spec:
type: vm # lowers to kind: VirtualMachine
vm: # == kind: VirtualMachine spec (examples/vm.yaml)
disk: delonix-vm-k8s:1.34
vcpus: 2
memory: 4G
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: web-app
spec:
type: pod # lowers to kind: Pod (real multi-container pod)
pod: # == kind: Pod spec (examples/pod-multi.yaml)
containers:
- { name: web, image: nginx:latest }
- { name: sidecar, image: busybox:latest }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: fast-vm
spec:
type: microvm # lowers to kind: VirtualMachine, forcing the microVM hypervisor
microvm: # == kind: VirtualMachine spec, but backend is forced to cloud-hypervisor
disk: ubuntu-24.04.qcow2 # needs a CH-bootable image (not the libvirt-only k8s golden)
vcpus: 2
memory: 2G
# backend: libvirt # <- a contradiction: type: microvm rejects it (use type: vm)
Todas as possibilidades — examples/full-workload.yamlEvery option — examples/full-workload.yaml
# kind: Workload — ONE declarative object for every compute type (ADR-0001/0006).
#
# delonix stack apply -f examples/full-workload.yaml --dry-run # shows what each Workload lowers to
# delonix stack validate -f examples/full-workload.yaml
#
# A Workload is sugar: it does not survive the load. `spec.type` picks the target Kind and the block
# named after the type carries EXACTLY that Kind's spec (no field is redefined here):
#
# type: container -> spec.container == kind: Container spec (full-container.yaml)
# type: vm -> spec.vm == kind: VirtualMachine spec (full-virtualmachine.yaml)
# type: pod -> spec.pod == kind: Pod spec (full-pod.yaml)
# type: microvm -> spec.microvm == kind: VirtualMachine spec, backend FORCED to cloud-hypervisor
#
# Exactly ONE block, the one matching `type` (any other block is an error). `metadata` (name, namespace,
# labels, annotations) is inherited by the lowered resource. Because each type is an alternative, every
# type gets its own document below; each block is shown with all its fields.
# ---------------------------------------------------------------------------
# Supporting resources
# ---------------------------------------------------------------------------
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: wl-net
spec:
driver: bridge
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: wl-secret
spec:
stringData:
API_TOKEN: example-token-not-real
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: wl-data
spec:
driver: local
---
# ---------------------------------------------------------------------------
# type: container — the FLAT container spec, every field
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-container
namespace: default # inherited by the lowered Container
labels: { tier: backend }
annotations: { example.delonix.io/note: lowers-to-container }
spec:
type: container
container:
image: nginx:alpine # REQUIRED
detach: true
command: ["nginx", "-g", "daemon off;"]
entrypoint: /docker-entrypoint.sh
user: "101:101"
hostname: wl-container-host
restartPolicy: always
addHost: ["legacy-api:10.0.0.9"]
labels: ["team=platform"]
logDriver: json
network: wl-net
ports: ["18090:80"]
expose: 80
networkAlias: [wl-web]
netBps: 10mbit
netBurst: 20mbit
memory: 256M
cpus: "0.5"
cpuWeight: "200"
cpuset: "0"
ioWeight: "100"
cgroupParent: { name: wl-tier, memoryMax: "1073741824", cpus: "2", pidsMax: "512" }
readOnly: true
capDrop: ["ALL"]
capAdd: ["NET_BIND_SERVICE"]
securityOpt: ["no-new-privileges"]
userns: true
volumes: ["wl-data:/var/lib/app"]
tmpfs: ["/tmp"]
env: ["TZ=Africa/Luanda"]
secret: [wl-secret]
secretFiles: true
ulimit: ["nofile=1024:2048"]
sysctl: ["net.core.somaxconn=1024"]
---
# ---------------------------------------------------------------------------
# type: container — the GROUPED container spec (resources/network/security/storage/env/limits)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-container-grouped
spec:
type: container
container:
image: busybox:latest
command: ["sleep", "infinity"]
restartPolicy: unless-stopped # no | on-failure[:max] | always | unless-stopped
resources: { memory: 128M, cpus: "0.25", cpuWeight: "100", ioWeight: "50" }
network: { name: wl-net, alias: [wl-grouped], rateBps: 5mbit, rateBurst: 10mbit }
security: { readOnly: true, capDrop: [ALL], userns: true }
storage: { volumes: ["wl-data:/data"], tmpfs: ["/tmp"] }
env: { vars: ["MODE=grouped"], secrets: [wl-secret], secretFiles: false }
limits: { ulimit: ["nofile=512:1024"], sysctl: ["net.core.somaxconn=512"] }
---
# ---------------------------------------------------------------------------
# type: pod — a multi-container pod (k8s-shaped), every pod-level field
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-pod
spec:
type: pod
pod:
network: wl-net # SDN network of the pod's shared netns
restartPolicy: OnFailure # Always | OnFailure | Never
hostname: wl-pod-host
shareProcessNamespace: true # members see each other's processes
hostAliases:
- ip: 10.0.0.9
hostnames: [legacy-api]
volumes:
- name: scratch
emptyDir: { medium: Memory }
- name: data
persistentVolumeClaim: { claimName: wl-data }
- name: host-files
hostPath: { path: /srv/www }
containers:
- name: web
image: nginx:latest
command: ["/bin/sh", "-c"]
args: ["nginx -g 'daemon off;'"]
workingDir: /usr/share/nginx/html
ports:
- { containerPort: 80, hostPort: 18091, protocol: TCP, hostIP: 127.0.0.1 }
env:
- { name: TZ, value: Africa/Luanda }
volumeMounts:
- { name: scratch, mountPath: /tmp/cache }
- { name: data, mountPath: /var/lib/web }
- { name: host-files, mountPath: /usr/share/nginx/html, readOnly: true }
resources:
limits: { cpu: "500m", memory: 256Mi }
securityContext:
privileged: false
runAsUser: 101
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"], add: ["NET_BIND_SERVICE"] }
- name: sidecar
image: busybox:latest
command: ["sleep", "infinity"]
---
# ---------------------------------------------------------------------------
# type: vm — a full VirtualMachine spec (libvirt backend), flat shape
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-vm
spec:
type: vm
vm:
disk: delonix-vm-base:ubuntu-24.04
backend: libvirt # `vm` accepts any backend (unlike `microvm`)
restartPolicy: always
vcpus: 2
memory: 4G
hugepages: false
cpuAffinity: "0-1"
network: wl-net
netMode: nat
ip: 192.168.122.60
vnc: true
hostname: wl-vm
sshKeys: ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleExampleExampleExampleExampleExample admin@example"]
userData: "@/etc/delonix/examples/user-data.yaml"
volumes:
- { name: wl-data, mountPath: /mnt/data, readOnly: false }
machine: q35
cpuModel: host-model
cpuTopology: { sockets: 1, cores: 2, threads: 1 }
tpm: true
video: virtio
bootOrder: [hd]
extraDisks:
- { source: /var/lib/libvirt/images/wl-extra.qcow2, bus: virtio, format: qcow2, readOnly: false, target: vdb }
extraNics:
- { type: network, source: default, model: virtio }
libvirtXmlOverlay:
- "<memballoon model='virtio'/>"
---
# ---------------------------------------------------------------------------
# type: vm — the GROUPED VirtualMachine spec (resources/network/cloudInit/libvirt)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-vm-grouped
spec:
type: vm
vm:
disk: delonix-vm-base:ubuntu-24.04
resources: { vcpus: 2, memory: 2G }
network: { name: wl-net, mode: nat, staticIp: 192.168.122.61 }
cloudInit: { hostname: wl-vm-grouped }
libvirt: { backend: libvirt, machine: q35 }
---
# ---------------------------------------------------------------------------
# type: microvm — a VirtualMachine on the microVM hypervisor; the backend is FORCED to cloud-hypervisor
# (writing `backend: libvirt` here is a contradiction and is rejected — use `type: vm` for that)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-microvm
spec:
type: microvm
microvm:
disk: delonix-vm-base:ubuntu-24.04 # needs an image that boots on Cloud Hypervisor (not the libvirt-only k8s golden)
backend: cloud-hypervisor # optional: only the value cloud-hypervisor is accepted here
vcpus: 2
memory: 2G
network: wl-net
firmware: /usr/local/share/delonix/CLOUDHV.fd
hostname: wl-microvm
restartPolicy: on-failure
hugepages: false
cpuAffinity: "2-3"
sshKeys: ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleExampleExampleExampleExampleExample admin@example"]Dependency
Alcançabilidade DIRIGIDA entre containers (ao contrário da rede, que é bidireccional): from alcança to, e to não fica exposto aos outros. É açúcar reduzido no load para kind: NetworkPolicy — sem dataplane novo. Várias dependências para o mesmo alvo ACUMULAM os allows (por isso são fundidas por alvo, e não uma política por dependência).
DIRECTED reachability between containers (unlike a network, which is bidirectional): from reaches to, and to stays unexposed to the others. Sugar lowered on load into kind: NetworkPolicy — no new dataplane. Several dependencies on the same target ACCUMULATE their allows, which is why they are merged by target rather than one policy per dependency.
# kind: Dependency (alias: KnowDepends) — alcançabilidade DIRIGIDA entre containers.
# Ao contrário de uma Network (bidirecional), abre UM sentido: `from` alcança `to`,
# mas `to` NÃO inicia para `from`. Caso clássico: a app conhece a DB, a DB não
# conhece a app — a DB deixa de ficar exposta a todos os containers da rede.
#
# É AÇÚCAR, e agora diz-se: no `load` cada Dependency é reescrita para um
# `kind: NetworkPolicy` com `direction: ingress`, default-deny no `to` + um allow
# por `from`. Corre `stack apply --dry-run` para ver a política real — antes desta
# fusão a tradução era invisível, e um `to` nomeado por uma Dependency E por uma
# FirewallPolicy obrigava o motor a AVISAR sobre o seu próprio gémeo.
#
# Várias Dependency para o mesmo `to` acumulam: fundem-se numa só política (a
# anotação `delonix.io/from-dependencies` regista de quais vieram). A regra nomeia
# o peer por WORKLOAD (`fromWorkload`), não por IP — o endereço de um container
# vem da SDN e muda num restart, por isso uma regra escrita como CIDR é uma regra
# que deixa de casar com o workload para que foi escrita.
# Aplicar com `delonix stack apply -f dependency.yaml`.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: db }
spec:
network: appnet
containers:
- name: db
image: "postgres:16"
env:
- { name: POSTGRES_PASSWORD, value: dev }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: app }
spec:
network: appnet
containers:
- name: app
image: "alpine:3.19"
command: ["sleep", "infinity"]
---
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: app-knows-db }
spec:
from: app # quem inicia (o "conhecedor")
to: db # alvo protegido (aceita um nome ou uma lista: [db, cache])
ports: ["5432"] # opcional — vazio = qualquer porta
proto: tcp # opcional — default any
Todas as possibilidades — examples/full-dependency.yamlEvery option — examples/full-dependency.yaml
# Complete reference for `kind: Dependency` (alias `KnowDepends`) — DIRECTED reachability
# between workloads. `from` may open connections to `to`; `to` does not initiate towards
# `from`, and `to` stops being exposed to everyone else on the network.
#
# It is sugar: at load each Dependency is rewritten into a `kind: NetworkPolicy` with
# `direction: ingress` on the `to` workload (default-deny + one allow per `from`, naming the
# peer by WORKLOAD so it survives restarts). Several Dependencies for the same `to` accumulate
# and merge into one policy. Run `delonix stack apply --dry-run` to see the real policy.
#
# spec fields: from (required, string), to (string or list), ports (list), proto.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec: { driver: bridge }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: db }
spec:
network: appnet
containers:
- name: db
image: "postgres:16-alpine"
env:
- { name: POSTGRES_PASSWORD, value: example }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: cache }
spec:
network: appnet
containers:
- name: cache
image: "redis:7-alpine"
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: app }
spec:
network: appnet
containers:
- name: app
image: "alpine:3.19"
command: ["sleep", "infinity"]
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: worker }
spec:
network: appnet
containers:
- name: worker
image: "alpine:3.19"
command: ["sleep", "infinity"]
---
# 1. Every field, `to` as a single name: app may reach db on TCP/5432 only.
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: app-knows-db }
spec:
from: app # who INITIATES (the one that "knows")
to: db # the protected target
ports: ["5432"] # ports of `to` opened to `from`; empty/omitted = any port
proto: tcp # tcp | udp | any (default any)
---
# 2. `to` as a LIST: one declaration protecting two targets at once.
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: app-knows-cache }
spec:
from: app
to: [cache, worker]
ports: ["6379", "9000"] # several ports
proto: tcp
---
# 3. Accumulation: a second `from` for the same `to` (db) — merged with document 1,
# so db accepts both app (5432/tcp) and worker (any port, any protocol).
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: worker-knows-db }
spec:
from: worker
to: dbNetworkRoute
O grau ACIMA do Dependency: aquele liga dois WORKLOADS, este liga duas REDES — isoladas por omissão. Uma rota diz que o pacote PODE atravessar; nunca diz que é PERMITIDO — quem decide isso continua a ser o FirewallPolicy/Dependency em cada extremidade. São duas perguntas em série (há caminho? → é aceite?), por isso são dois Kinds e não um campo routes: dentro de Network. Dirigida (from inicia, o retorno flui; to nunca inicia de volta); custo constante, não cresce com o número de redes.
The grade ABOVE Dependency: that one links two WORKLOADS, this one links two NETWORKS — isolated by default. A route says the packet CAN cross; it never says it's ALLOWED — that decision still belongs to FirewallPolicy/Dependency at each end. Two questions in series (is there a path? → is it accepted?), which is why they're two Kinds and not one routes: field inside Network. Directed (from initiates, the return flows; to never initiates back); constant cost, doesn't grow with the number of networks.
# kind: NetworkRoute — caminho DIRIGIDO entre duas REDES (ADR-0013 tier B).
#
# Redes são isoladas umas das outras por omissão. Isto declara que uma pode
# alcançar a outra. Dirigido, como o `kind: Dependency` entre containers: `from`
# inicia, e o retorno dessa conversa flui (established); `to` NÃO inicia de volta.
#
# ┌─ A REGRA QUE FAZ ISTO COMPOR-SE, e não minar o isolamento ──────────────────┐
# │ Uma rota diz que o pacote PODE atravessar. Nunca diz que é PERMITIDO. │
# │ As chains `fwcont` por workload continuam a decidir, e uma fronteira de │
# │ namespace atravessada por uma rota continua a precisar da sua política. │
# └─────────────────────────────────────────────────────────────────────────────┘
#
# São duas perguntas em série, e é por isso que são dois Kinds e não um:
#
# fwdeny (-10) ← NetworkRoute: as duas redes têm caminho? senão: drop
# fwcont (-5) ← FirewallPolicy: este workload aceita isto? senão: drop
#
# Sem a rota, o `api` nem é alcançável do `web` (redes diferentes). Com a rota e
# sem a Dependency abaixo, o caminho existe e o `api` continua a decidir sozinho
# quem aceita. Este ficheiro declara as duas coisas de propósito — tirar uma
# delas e reaplicar é a melhor forma de ver a diferença.
#
# Por baixo é UM elemento no verdict map `@netpair` do holder (uma isenção ao
# drop par-a-par entre bridges), não um dataplane novo. Custo constante: não
# cresce com o número de redes.
#
# Documento próprio, e não um campo `routes:` dentro do `kind: Network`, porque
# uma rota é uma RELAÇÃO e não pertence a nenhuma das pontas — exprimível dos
# dois lados é como dois documentos passam a discordar sobre a mesma rota, o bug
# que o `FirewallPolicy` já paga ao RECUSAR duas políticas para o mesmo alvo e
# direcção.
#
# O `via:` que aparece no rascunho do ADR-0013 NÃO existe: o spike mostrou que o
# encaminhamento já lá estava e que fechar é que era explícito, por isso o que
# sobrou foi `from`/`to`. Os campos aceites são só esses dois.
#
# Aplicar com `delonix stack apply -f netroute.yaml`.
# Imperativo equivalente: delonix network route <from> <to> (e `--rm` para fechar)
#
# E FECHA-SE tirando o documento daqui: a rota converge, tem posse
# (`delonix.io/stack`) e sai com um `stack apply --prune` ou um `stack destroy`.
# Uma rota criada à mão pelo comando imperativo não leva carimbo e sobrevive aos
# dois — um apply nunca remove o que não criou.
#
# A identidade é o PAR e não o `metadata.name`: renomear este documento não muda
# nada, o plano imprime `NetworkRoute web->api`, e dois documentos a declarar o
# mesmo par são recusados.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: frontend }
spec:
driver: bridge
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: backend }
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata: { name: web }
spec:
image: "alpine:3.19"
network: frontend
command: ["sleep", "infinity"]
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata: { name: api }
spec:
image: "alpine:3.19"
network: backend
command: ["sleep", "infinity"]
---
# 1ª pergunta: HÁ CAMINHO? — frontend alcança backend, e não o contrário.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkRoute
metadata: { name: frontend-to-backend }
spec:
from: frontend
to: backend
---
# 2ª pergunta: É PERMITIDO? — a rota não abriu o `api` a ninguém; isto abre-o
# ao `web` e só a ele (default-deny no `api` + um allow para o `web`).
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: web-knows-api }
spec:
from: web
to: api
ports: ["8080"]
proto: tcp
Todas as possibilidades — examples/full-networkroute.yamlEvery option — examples/full-networkroute.yaml
# Complete reference for `kind: NetworkRoute` — a DIRECTED path between two NETWORKS.
# The spec has exactly two fields: `from` and `to`. There is no `via`.
#
# A route only says the packet MAY cross between the two bridges; it never says
# it is ALLOWED. Per-workload policy (NetworkPolicy / Dependency / NetworkAccessRule)
# still decides, so the last two documents show the second question answered.
# The identity of a route is the PAIR (from->to), not metadata.name; two documents
# declaring the same pair are refused. `stack apply --prune` closes routes removed
# from the manifest.
---
# Networks the routes connect (referenced by name; they must be declared here).
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: frontend }
spec: { driver: bridge }
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: backend }
spec: { driver: bridge }
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: mgmt }
spec: { driver: bridge }
---
# 1. One-way path: hosts on `frontend` may initiate towards `backend`.
# Return traffic of those conversations flows; `backend` cannot initiate back.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkRoute
metadata:
name: frontend-to-backend
labels:
purpose: app-traffic # metadata is free-form; only spec.from/spec.to matter
spec:
from: frontend # network that may INITIATE
to: backend # network reached
---
# 2. A second, independent pair: the management network may reach both others.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkRoute
metadata: { name: mgmt-to-backend }
spec:
from: mgmt
to: backend
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkRoute
metadata: { name: mgmt-to-frontend }
spec:
from: mgmt
to: frontend
---
# 3. A bidirectional path is two explicit routes (the reverse pair, declared on purpose).
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkRoute
metadata: { name: backend-to-mgmt }
spec:
from: backend
to: mgmt
---
# Workloads, so that the "is it allowed?" question below has something to protect.
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: web }
spec:
network: frontend
containers:
- name: web
image: "alpine:3.19"
command: ["sleep", "infinity"]
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: api }
spec:
network: backend
containers:
- name: api
image: "alpine:3.19"
command: ["sleep", "infinity"]
---
# 4. The 2nd question, in series with the route: is the crossing ACCEPTED by `api`?
# Without this the path exists but `api` still decides who it accepts.
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: web-knows-api }
spec:
from: web
to: api
ports: ["8080"]
proto: tcpNetworkPolicy
Firewall L4 por container, estilo NetworkPolicy do k8s, com a direcção em spec.direction. Aplicar substitui as regras dessa direcção e deixa a outra intacta.
Per-container L4 firewall, k8s NetworkPolicy-style, with the direction in spec.direction. Applying replaces that direction's rules and leaves the other intact.
# kind: NetworkPolicy — a forma UNIFICADA do firewall declarativo. A direcção
# vem de `spec.direction` (ingress|egress) em vez do nome do Kind. Resolve a
# confusão de que, aqui, `kind: Ingress` é firewall L4 (não o Ingress L7/HTTP do
# k8s). `kind: Egress` foi REMOVIDO; o load recusa-o nomeando esta forma — este é
# só a superfície unificada equivalente.
#
# delonix stack apply -f examples/firewallpolicy.yaml
# ---------------------------------------------------------------------------
# Os recursos que as políticas abaixo referenciam. Estão aqui DE PROPÓSITO: um
# exemplo que nomeia um container inexistente dá `unresolved reference(s)` a
# quem o copia, e copiar é a primeira coisa que se faz com um exemplo.
# ---------------------------------------------------------------------------
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: sdn-demo
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: dbapp
spec:
image: postgres:16-alpine
network: sdn-demo # a firewall L4 só actua em containers na SDN
env:
- POSTGRES_PASSWORD=exemplo
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata:
name: db-inbound
spec:
direction: ingress # = kind: Ingress
target: dbapp # container-alvo (na SDN)
defaultPolicy: deny
rules:
- proto: tcp
port: "5432"
from: 10.219.0.0/16
note: postgres-from-sdn
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata:
name: db-outbound
spec:
direction: egress # o kind: Egress foi removido
target: dbapp
defaultPolicy: deny
rules:
- proto: udp
port: "53"
note: dns
- proto: tcp
port: "443"
to: 0.0.0.0/0
note: https
Todas as possibilidades — examples/full-networkpolicy.yamlEvery option — examples/full-networkpolicy.yaml
# Complete reference for `kind: NetworkPolicy` — declarative L4 firewall.
# One document = the DESIRED STATE of ONE direction (`ingress` or `egress`) of ONE
# target. Applying it REPLACES that direction's rules; the other direction is untouched,
# so an ingress and an egress document compose on the same workload. Two documents for
# the same (target, direction) are refused. It only acts on workloads on a custom SDN network.
#
# spec fields: direction, scope, target, defaultPolicy, rules[], allowCidrs,
# fqdnAllowlist, rateLimit. Per-rule fields: proto, port, action, from, fromWorkload,
# to, toWorkload, note. `allowCidrs`/`fqdnAllowlist`/`rateLimit` are only for
# `scope: network` (documents 5 and 6); `rules` is only for `scope: container`.
---
# Resources the policies refer to (must exist in the same manifest).
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: sdn-demo }
spec: { driver: bridge }
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: sdn-locked }
spec: { driver: bridge }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: db }
spec:
network: sdn-demo
containers:
- name: db
image: "postgres:16-alpine"
env:
- { name: POSTGRES_PASSWORD, value: example }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: app }
spec:
network: sdn-demo
containers:
- name: app
image: "alpine:3.19"
command: ["sleep", "infinity"]
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: cache }
spec:
network: sdn-demo
containers:
- name: cache
image: "redis:7-alpine"
---
# 1. INGRESS allowlist (defaultPolicy deny) using every ingress-side rule form.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: db-inbound }
spec:
direction: ingress # ingress (inbound) | egress (outbound)
scope: container # container (default): `target` is a workload name
target: db # workload the policy protects
defaultPolicy: deny # allow | deny when no rule matches (default for scope container: deny)
rules:
- proto: tcp # tcp | udp | any (default any)
port: "5432" # a single port
from: 10.219.0.0/16 # source CIDR
action: allow # allow (default) | deny
note: postgres-from-sdn # free-text label, shown in listings
- proto: tcp
port: "5432"
fromWorkload: app # source BY WORKLOAD NAME (resolved to its SDN address at apply;
# survives restarts, unlike a CIDR). Exclusive with from/to.
note: postgres-from-app
- proto: udp
port: "8000-8100" # a port range n-m
from: 10.219.5.0/24
note: udp-range
- proto: any # any protocol
port: "*" # any port
from: 10.219.9.9/32
action: deny # an explicit deny, evaluated by rule order
note: block-this-host
---
# 2. INGRESS with defaultPolicy allow: only the listed denies apply.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: cache-inbound-open }
spec:
direction: ingress
target: cache
defaultPolicy: allow # everything not listed is accepted
rules:
- port: "6379" # proto omitted = any
from: 203.0.113.0/24
action: deny
note: no-external-redis
---
# 3. EGRESS allowlist for a workload, with a CIDR, a workload name and a wildcard.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: app-outbound }
spec:
direction: egress
target: app
defaultPolicy: deny
rules:
- proto: udp
port: "53"
note: dns
- proto: tcp
port: "443"
to: 0.0.0.0/0 # destination CIDR (egress-side counterpart of `from`)
note: https-anywhere
- proto: tcp
port: "5432"
toWorkload: db # destination BY WORKLOAD NAME (egress counterpart of fromWorkload)
note: postgres-to-db
---
# 4. Minimal egress: no rules, default deny => the workload cannot open outbound connections.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: cache-outbound-locked }
spec:
direction: egress
target: cache
defaultPolicy: deny
---
# 5. PER-NETWORK egress (scope: network, egress only): `target` is a NETWORK name.
# CIDR allowlist + FQDN allowlist (learnt live from DNS) + global L4 rate limit.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: sdn-locked-egress }
spec:
direction: egress
scope: network # container (default) | network
target: sdn-locked # network name
defaultPolicy: deny # allowCidrs/fqdnAllowlist only make sense with deny
allowCidrs: # destinations allowed besides DNS
- 10.0.0.0/8
- 198.51.100.0/24
fqdnAllowlist: # hostnames allowed (and *.host), learnt via DNS snooping
- registry.example.com
- updates.example.org
rateLimit: # GLOBAL L4 guard of the rootless ingress (not per network)
connRate: 100 # new connections per second allowed
connMax: 2000 # maximum concurrent connections
---
# 6. PER-NETWORK egress fully open (defaultPolicy allow, nothing else).
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: sdn-demo-egress-open }
spec:
direction: egress
scope: network
target: sdn-demo
defaultPolicy: allowIngress
Ingress L7 no formato networking.k8s.io/v1 (host/path → backend), compilado para o proxy embutido. Limitações herdadas: um só certificado (sem SNI) e pathType: Exact tratado como prefixo.
L7 Ingress in the networking.k8s.io/v1 shape (host/path → backend), compiled to the built-in proxy. Inherited limitations: one certificate only (no SNI) and pathType: Exact treated as a prefix.
# kind: Ingress — Kubernetes-shaped L7/HTTP Ingress (host/path → backend service).
# This is the networking.k8s.io/v1 Ingress schema; it compiles to the embedded L7
# reverse-proxy (same engine as kind: HTTPRoute). Apply with:
# delonix net httproute apply -f examples/ingress.yaml (or `stack apply`)
#
# NOTE: `kind: Ingress` used to be the L4 firewall — that moved to
# `kind: NetworkPolicy` (direction: ingress); see examples/firewall.yaml.
# Backends must be containers WITH an IP on a custom SDN network (not --net host/none).
# ---------------------------------------------------------------------------
# Os backends e o certificado que a rota abaixo referencia. Estão aqui DE
# PROPÓSITO: sem eles o exemplo não valida, e quem o copia recebe
# `unresolved reference(s)` em vez de um ingress a funcionar.
# ---------------------------------------------------------------------------
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: sdn-demo
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web
spec:
image: nginx:alpine
network: sdn-demo # o proxy L7 só alcança backends na SDN
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: api
spec:
image: caddy:alpine
network: sdn-demo
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: shop-tls
stringData:
# Substitui pelo teu par real. Para experimentar sem certificado, apaga o
# bloco `tls:` da rota e serve em HTTP.
tls_crt: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
tls_key: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
---
apiVersion: gateway.delonix.io/v1alpha1
kind: Ingress
metadata:
name: shop
spec:
ingressClassName: delonix # accepted for k8s fidelity (the embedded proxy)
tls: # v1 serves a SINGLE cert (no SNI) — first entry wins
- hosts: [shop.example.ao]
secretName: shop-tls # a kind: Secret with tls.crt/tls.key; omit → self-signed
rules:
- host: shop.example.ao
http:
paths:
- path: /
pathType: Prefix # matched by prefix (Exact accepted, treated as prefix)
backend:
service:
name: web # a container name (resolved to its SDN IP at apply)
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 8080
# defaultBackend: # optional catch-all when no rule matches
# service: { name: web, port: { number: 80 } }
Todas as possibilidades — examples/full-ingress.yamlEvery option — examples/full-ingress.yaml
# Complete reference for `kind: Ingress` — the Kubernetes networking.k8s.io/v1 Ingress shape
# (host/path -> backend service). It compiles to the SAME embedded L7 proxy as `kind: HTTPRoute`
# and is composed with every other Ingress/HTTPRoute of the manifest into ONE proxy with a SINGLE
# certificate (no SNI). Backends must be workloads with an IP on a custom SDN network.
# (The L4 firewall that used to live under this Kind is `kind: NetworkPolicy`.)
#
# spec fields used here: entrypoints[] (port, tls), tls[] (secretName), rules[] (host,
# http.paths[] (path, pathType, backend.service (name, port.number))), defaultBackend.service.
# Not shown because they have no effect: `ingressClassName` (the embedded proxy is the only
# class), `tls[].hosts` (reported as not applied), `pathType: Exact` (served as a prefix and
# warned), a `tls` list longer than one (only the first is used) and named ports (`port.name`
# is refused: use `port.number`).
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: sdn-demo }
spec: { driver: bridge }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: web }
spec:
network: sdn-demo
containers:
- name: web
image: "nginx:alpine"
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: api }
spec:
network: sdn-demo
containers:
- name: api
image: "caddy:alpine"
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: docs }
spec:
network: sdn-demo
containers:
- name: docs
image: "httpd:2.4-alpine"
---
# Certificate presented by the TLS listener (placeholders — use a real pair).
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata: { name: shop-tls }
stringData:
tls_crt: "-----BEGIN CERTIFICATE-----\nplaceholder\n-----END CERTIFICATE-----"
tls_key: "-----BEGIN PRIVATE KEY-----\nplaceholder\n-----END PRIVATE KEY-----"
---
# 1. Full Ingress: explicit listeners, a TLS certificate, two hosts, several paths and a default backend.
apiVersion: gateway.delonix.io/v1alpha1
kind: Ingress
metadata: { name: shop }
spec:
entrypoints: # delonix extension: listener ports. Omitted = 80 (+443 when tls is set)
- { port: 80 }
- { port: 443, tls: true } # terminate TLS on this port
tls: # a LIST in k8s; this engine serves a single certificate
- secretName: shop-tls # kind: Secret with tls_crt/tls_key (or tls.crt/tls.key); omit = self-signed
rules:
- host: shop.example.ao # Host header to match; omit = any host
http:
paths:
- path: /
pathType: Prefix # matched by prefix
backend:
service:
name: web # workload name, resolved to its SDN IP at apply
port:
number: 80 # container port (named ports are not supported)
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 8080
- host: docs.example.ao # a second host in the same Ingress
http:
paths:
- path: / # `path` defaults to /
backend:
service:
name: docs
port:
number: 80
defaultBackend: # catch-all when no rule matches
service:
name: web
port:
number: 80
---
# 2. A minimal second Ingress joining the same proxy: no TLS, no entrypoints (defaults to port 80).
apiVersion: gateway.delonix.io/v1alpha1
kind: Ingress
metadata: { name: status }
spec:
rules:
- host: status.example.ao
http:
paths:
- path: /healthz
backend:
service:
name: api
port:
number: 8080Stack
Agrupa vários recursos num só documento. Expandido no load para os Kinds individuais, em ordem de dependência — o Stack não sobrevive ao load, tudo o resto vê os filhos.
Groups several resources into one document. Expanded on load into the individual Kinds, in dependency order — the Stack doesn't survive the load, everything else sees the children.
# kind: Stack — groups a whole app (networks, containers, volumes, …) in ONE
# document (k8s-Service-like). Apply with:
# delonix stack apply -f examples/stack.yaml
# At load time the Stack expands into its constituent resources, which then flow
# through the normal per-Kind apply IN DEPENDENCY ORDER — the order
# `delonix stack plan --fields` prints, which is the order of the Kind table and
# not a list kept here. Each child inherits the Stack's namespace unless it sets
# its own, and can carry `labels:`/`annotations:` next to `name:`. You can still write the resources as separate documents
# — the Stack is just a convenient single place to bundle them.
#
# This example uses EVERY group the Stack accepts (the test
# `the_stack_example_names_every_group` fails if one is missing), so it doubles as the reference
# for what each one looks like. For a single Kind with all of its options, see
# examples/full-<kind>.yaml.
#
# A group item is `{ name, namespace?, labels?, annotations?, spec }`: `spec` is exactly the `spec` of
# the Kind the group stands for, and `namespace` overrides the Stack's for that
# one item.
#
# group Kind full reference
# runtimePolicies RuntimePolicy full-runtimepolicy.yaml
# secrets Secret full-secret.yaml
# networks Network full-network.yaml
# networkRoutes NetworkRoute full-networkroute.yaml
# volumes Volume full-volume.yaml
# images Image full-image.yaml
# apps App full-app.yaml
# vms VirtualMachine full-virtualmachine.yaml
# containers Container full-container.yaml
# pods Pod full-pod.yaml
# services Service full-service.yaml
# ipPools IPPool full-ippool.yaml
# ingress Ingress full-ingress.yaml
# firewallPolicies NetworkPolicy full-networkpolicy.yaml
# networkAccessRules NetworkAccessRule full-networkaccessrule.yaml
# httpRoutes HTTPRoute full-httproute.yaml
# gateways Gateway full-gateway.yaml
# workloads Workload full-workload.yaml (lowered at load)
# dependencies Dependency full-dependency.yaml (lowered at load)
#
# `tunnels:` is the old spelling of `gateways:` and still loads. Not groupable:
# `Stack` (a Stack does not nest) and `KubernetesCluster` (a remote procedure
# over SSH, not a resource of this node — see full-kubernetescluster.yaml).
#
# The groups that used to exist for the removed Kinds are gone: NFS/CIFS/WebDAV
# storage and shares are a `volumes:` item with an `nfs:`/`cifs:`/`webdav:` or
# `share:` block; an egress policy is a `firewallPolicies:` item with
# `direction: egress`.
apiVersion: core.delonix.io/v1alpha1
kind: Stack
metadata:
name: blog
namespace: default
spec:
# The node's admission ceiling, first of all: it applies before every other
# group below, in the SAME apply (see full-runtimepolicy.yaml). Lowering it
# is never automatic — removing this block and re-applying leaves
# policy.json untouched; `delonix policy unset` is the only way down.
runtimePolicies:
- name: node-ceiling
spec:
denyPrivileged: true
denyLatestTag: true
# Secrets first: everything below may reference them.
secrets:
- name: db-credentials
spec:
stringData:
POSTGRES_PASSWORD: change-me
networks:
- name: blog-net
spec:
driver: bridge
- name: blog-back
spec:
driver: bridge
# A route says the two networks HAVE a path; the policies below still decide
# what is allowed across it.
networkRoutes:
- name: net-to-back
spec:
from: blog-net
to: blog-back
# Cluster-native SDN — realized by whichever NetworkZoneProvider the
# RUNTIME has configured (today: Proxmox, via DELONIX_PROXMOX_URL), never
# named here. Applying this without a provider configured refuses with a
# clear error naming what to set (ADR-0049 addendum).
networkZones:
- name: dc1
spec:
vnets:
- name: prod
alias: "Production VLAN"
volumes:
- name: db-data
spec:
quota: 5G # hard cap on the volume's data
alertPct: 80 # warn when it is 80% full
images:
- name: alpine-base
spec:
pull: alpine:3.20 # `pull` and `build` are alternatives; see full-image.yaml
# Cloud Native Buildpacks: source in, image out (see full-app.yaml).
apps:
- name: shop
spec:
source: .
image: shop:latest
# A microVM/VM on the same SDN. Needs a VM image or disk on the host to apply.
vms:
- name: worker
spec:
disk: delonix-vm-base:ubuntu-24.04
vcpus: 1
memory: 1G
network: blog-net
# A system container: a whole userland on the runtime's configured remote
# provider (today: a Proxmox node, via DELONIX_PROXMOX_URL), never named here.
# The engine pulls the image; memory/swap/cores change in place, the rest
# recreates (ADR-0058).
systemContainers:
- name: tools
spec:
image: alpine:3.20
entrypoint: ["/bin/sleep", "infinity"]
memory: 256M
cores: 1
containers:
- name: web
labels: { app: web } # what the Service below selects by
spec:
image: nginx:latest
network: blog-net
ports: ["8080:80"]
expose: 80 # auto-register in the L7 proxy as web.<namespace>.delonix.internal
- name: db
spec:
image: postgres:16
network: blog-net
secret: [db-credentials]
volumes: ["db-data:/var/lib/postgresql/data"]
# A pod: several containers sharing ONE network namespace (localhost between them).
pods:
- name: worker-pod
spec:
containers:
- name: app
image: nginx:alpine
- name: sidecar
image: alpine:3.20
command: ["sleep", "infinity"]
# Round-robin DNS over every container the selector matches, under
# web.<namespace>.delonix.internal. No VIP, no daemon.
services:
- name: web
spec:
selector: { matchLabels: { app: web } }
port: 80
# A set of host addresses a route can claim with `spec.pool`. Only announce: local is
# built: the address must already be on an interface of the host.
ipPools:
- name: edge
spec:
addresses: ["127.0.0.10-127.0.0.12"]
# Kubernetes-shaped L7 Ingress: host/path → backend service.
ingress:
- name: blog-ingress
spec:
rules:
- host: blog.example.ao
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: web, port: { number: 80 } }
# Per-container L4 firewall. Each item is the WHOLE desired state of one direction.
firewallPolicies:
# (`db` ingress is owned by the Dependency below, and two policies for the same
# target+direction are refused — so this one protects `web`.)
- name: web-ingress
spec:
target: web
direction: ingress
defaultPolicy: deny
rules:
- { action: allow, proto: tcp, port: "80", from: 10.0.0.0/8 }
- name: web-egress
spec:
target: web
direction: egress
defaultPolicy: allow
# ONE incremental rule: unlike a firewallPolicy it ADDS to the target's rules
# instead of replacing the direction, and removing it removes only itself.
networkAccessRules:
- name: web-metrics
spec:
target: web
direction: ingress
proto: tcp
port: "9113"
from: 10.0.0.0/8
# A perimeter alias+rule on an external GatewayProvider (ADR-0051) —
# `provider: opnsense` must already be registered (DELONIX_OPNSENSE_URL and
# a credential, `cmd::gatewayproviders`) before `stack apply` reaches here.
networkGateways:
- name: block-untrusted
spec:
provider: opnsense
aliases:
- name: trusted-net
kind: network
content: ["10.0.0.0/24"]
description: internal trusted range
rules:
- description: allow-trusted-to-web
source: trusted-net
destination: 10.0.0.0/8
protocol: TCP
# Native L7 reverse proxy, same engine as `ingress:` (both merge into one proxy).
httpRoutes:
- name: blog-api
spec:
entrypoints:
- { port: 8081 }
rules:
- host: api.blog.example.ao
paths:
- path: /
backend: { service: web, port: 80 }
# Directed reachability: `web` may reach `db`, `db` is not exposed to the others.
dependencies:
- name: web-knows-db
spec:
from: web
to: db
ports: ["5432"]
proto: tcp
# A Workload is lowered at load into the container/pod/VM its `type` names.
workloads:
- name: batch
spec:
type: container
container:
image: alpine:3.20
command: ["sleep", "infinity"]
# Public tunnel to a local port (kind: Gateway). pinggy needs no account.
gateways:
- name: blog-public
spec:
provider: pinggy
localPort: 8080
KubernetesCluster
Bootstrap kubeadm idempotente sobre hosts JÁ vivos, por SSH. Sem ficheiro de estado: cada passo tem um check, por isso nunca dessincroniza. Ver também cluster-vm.yaml (provisiona as VMs) e cluster-kind.yaml (modo kind).
Idempotent kubeadm bootstrap on hosts that are ALREADY alive, over SSH. No state file: every step has a check, so it can never drift. See also cluster-vm.yaml (provisions the VMs) and cluster-kind.yaml (kind mode).
# Cluster Kubernetes em **hosts remotos JÁ EXISTENTES** (bare-metal ou VMs de
# outrem), via SSH.
#
# delonix cluster apply -f examples/cluster-ssh.yaml
#
# O delonix NÃO cria estas máquinas — elas já têm de estar vivas e alcançáveis,
# com o utilizador SSH a ter `sudo` NOPASSWD. É o modo para datacenter.
#
# **Idempotente sem ficheiro de estado** ("Terraform sem .tfstate"): cada passo
# tem um `check` (comando shell; êxito = já satisfeito) e um `apply`. Correr
# duas vezes não faz nada de novo — e nunca dessincroniza de um estado paralelo,
# porque não existe nenhum.
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: prod
spec:
mode: ssh
# --- comum a todos os modos ---
k8sVersion: "1.34"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: none # em produção, normalmente instalas a TUA CNI (Cilium, Calico…)
# HA: com >1 control-plane, o `controlPlaneEndpoint` é OBRIGATÓRIO — o kubeadm
# precisa de um endereço estável (LB/VIP) à frente deles. O delonix não
# provisiona o LB; aponta para um que já tenhas.
controlPlaneEndpoint: "k8s-api.exemplo.ao:6443"
controlPlane:
hosts:
- address: 10.0.0.11
- address: 10.0.0.12
- address: 10.0.0.13
workers:
hosts:
- address: 10.0.0.21
- address: 10.0.0.22
# --- específico do modo ssh ---
ssh:
user: delonix # tem de ter sudo NOPASSWD nos hosts
keyPath: ~/.ssh/id_ed25519
port: 22
# Só `stacked` (etcd nos control-planes, o default do kubeadm) está suportado.
# `external` é reconhecido no schema mas recusado com erro claro — etcd externo
# (TLS entre membros, discovery) é um subprojecto à parte.
etcd:
mode: stacked
Todas as possibilidades — examples/full-kubernetescluster.yamlEvery option — examples/full-kubernetescluster.yaml
# Complete reference for `kind: KubernetesCluster` — the three modes, one document each.
# `mode` is the discriminator: `kind` (nodes as containers here), `vm` (golden-image VMs),
# `ssh` (remote hosts that already exist). Each mode reads only its own block
# (`kind:`, `vm:`, `ssh:`) plus the common fields.
#
# This Kind is NOT part of `stack apply` (a cluster is a remote procedure, not a node-local
# resource). Apply it with: delonix cluster apply -f examples/full-kubernetescluster.yaml
# Validate offline with: delonix manifest validate -f examples/full-kubernetescluster.yaml
---
# 1. mode: kind — a local cluster in containers, no Docker and no `kind` binary needed.
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: lab-kind
spec:
mode: kind
k8sVersion: "1.34" # Kubernetes minor version
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: default # default = the node image's CNI (kindnet); none = install none
controlPlane:
replicas: 1 # >1 requires controlPlaneEndpoint
workers:
replicas: 1
kind: # only read in mode: kind
image: kindest/node:v1.34.0 # node image (pin by digest for reproducibility)
apiServerPort: 6443 # host port for the apiserver
---
# 2. mode: vm — every node is a real microVM from the golden image.
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: lab-vm
spec:
mode: vm
k8sVersion: "1.34"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: default
controlPlane:
replicas: 1
workers:
replicas: 2
vm: # only read in mode: vm
image: k8s-golden # tag from `delonix image vm ls` (omit = the only local one)
network: lab-net # network created beforehand
vcpus: 2 # per node
memory: 2G # per node
sshKey: ~/.ssh/id_ed25519 # private key to reach the VMs (omit = a key is generated)
bootTimeout: 300s # wait for SSH after boot
---
# 3. mode: ssh — hosts that already exist (bare metal / someone else's VMs), HA control plane.
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: prod-ssh
spec:
mode: ssh
k8sVersion: "1.34"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: none # bring your own CNI (Cilium, Calico...)
controlPlaneEndpoint: "k8s-api.example.com:6443" # REQUIRED with >1 control-plane (LB/VIP)
controlPlane:
hosts: # `hosts` (not `replicas`): the machines already exist
- address: 10.0.0.11
- address: 10.0.0.12
- address: 10.0.0.13
workers:
hosts:
- address: 10.0.0.21
- address: 10.0.0.22
ssh: # only read in mode: ssh
user: delonix # needs passwordless sudo on the hosts
keyPath: ~/.ssh/id_ed25519 # canonical name (`key` is the legacy spelling)
port: 22
etcd:
mode: stacked # etcd on the control-planes (kubeadm default)
---
# 4. mode: ssh with a dedicated external etcd cluster (own CA, odd number of members).
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: prod-ssh-etcd
spec:
mode: ssh
k8sVersion: "1.34"
cni: none
controlPlaneEndpoint: "k8s-api.example.com:6443"
controlPlane:
hosts:
- address: 10.0.1.11
- address: 10.0.1.12
- address: 10.0.1.13
workers:
hosts:
- address: 10.0.1.21
ssh:
user: delonix
keyPath: ~/.ssh/id_ed25519
etcd:
mode: external # external etcd is only accepted with mode: ssh
hosts: # dedicated etcd hosts (odd count; exactly 1 = dev only)
- address: 10.0.2.11
- address: 10.0.2.12
- address: 10.0.2.13Network
Uma rede de utilizador. Os containers juntam-se com --net <nome>; as VMs com network:. Driver bridge é o único a que containers se atacham hoje.
A user network. Containers join it with --net <name>; VMs with network:. bridge is the only driver containers can attach to today.
# Network — a user-defined network. Containers join it with `--net <name>`.
# Apply with: delonix network apply -f examples/network.yaml
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: appnet
spec:
driver: bridge # bridge | macvlan | ipvlan | overlay
subnet: 10.89.0.0/24 # optional — auto-picked if omitted
gateway: "" # optional — defaults to .1 of the subnet
parent: null # NIC parent, for macvlan/ipvlan (e.g. eth0)
vni: null # VXLAN id, for overlay
peers: [] # overlay peers: "<ip>" or "<ip>=<wg_pubkey>=<wg_ip>"
wgIp: null # this node's WireGuard tunnel IP (encrypted overlay) (alias: wg_ip)
Todas as possibilidades — examples/full-network.yamlEvery option — examples/full-network.yaml
# Complete reference for `kind: Network` — every spec field and every driver form.
# Each document is independent; apply with `delonix stack apply -f full-network.yaml`.
# Fields: driver, subnet, gateway, parent, vni, peers, wgIp (alias wg_ip).
---
# 1. Bridge with every bridge-relevant field spelled out.
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-bridge-full
labels:
tier: backend # free-form labels (ownership stamps are added by stack apply)
annotations:
example.io/purpose: "all bridge fields"
spec:
driver: bridge # bridge (default) | macvlan | ipvlan | overlay
subnet: 10.201.0.0/16 # CIDR of the network; omit to auto-pick a free /16
gateway: 10.201.0.1 # gateway address; omit to default to .0.1 of the subnet
---
# 2. Bridge with every field omitted — driver defaults to bridge, subnet is auto-picked.
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-bridge-default
spec: {}
---
# 3. Overlay (VXLAN) between nodes: vni + peers, clear-text.
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-overlay-plain
spec:
driver: overlay
vni: 42 # VXLAN network identifier
peers: # other nodes of the overlay, by underlay IP
- 192.168.1.11
- 192.168.1.12
---
# 4. Encrypted overlay (WireGuard transport): peers carry "<ip>=<wg_pubkey>=<wg_ip>".
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-overlay-encrypted
spec:
driver: overlay
vni: 77
wgIp: 10.250.0.1 # this node's WireGuard tunnel IP (alias: wg_ip)
peers:
- "192.168.1.11=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==10.250.0.2"
---
# 5. macvlan: containers get an address directly on the parent NIC's L2 segment.
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-macvlan
spec:
driver: macvlan
parent: eth0 # host NIC the macvlan sits on
subnet: 192.168.50.0/24
gateway: 192.168.50.1
---
# 6. ipvlan: like macvlan but all containers share the parent's MAC.
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-ipvlan
spec:
driver: ipvlan
parent: eth0
subnet: 192.168.60.0/24Volume
Um volume local nomeado — os dados sobrevivem a container rm. Para armazenamento de REDE (NFS/SMB/WebDAV) o MESMO Kind leva um bloco nfs:/cifs:/webdav: — ver storage.yaml.
A named local volume — the data survives container rm. For NETWORK storage (NFS/SMB/WebDAV) the SAME Kind takes an nfs:/cifs:/webdav: block — see storage.yaml.
# Volume — a named local volume (data survives `container rm`). For NETWORK
# storage (NFS/SMB/WebDAV) the SAME Kind takes an `nfs:`/`cifs:`/`webdav:` block
# (see examples/storage.yaml).
# Apply with: delonix volume apply -f examples/volume.yaml
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: appdata
spec:
driver: local # local | nfs (for a NAS, prefer an `nfs:` block — storage.yaml)
quota: null # size cap, e.g. "2g" (hard cap as root, monitored in rootless)
device: null # nfs export "server:/export" (only with driver: nfs)
mountOptions: null # mount options, e.g. "vers=4,ro" (only with driver: nfs) (alias: options)
Todas as possibilidades — examples/full-volume.yamlEvery option — examples/full-volume.yaml
# Complete reference for `kind: Volume` — every variant of `spec`.
# One Kind, several MUTUALLY EXCLUSIVE shapes (hence one document each):
# 1. local volume (driver: local, or nothing)
# 2. raw network driver (driver: nfs + device + mountOptions)
# 3. nfs: / cifs: / webdav: network share blocks (the block NAME is the type)
# 4. share: a quota'd slice of another volume
# 5. provision.truenas: create the dataset/quota/export on a NAS, then mount it
# `quota` and `alertPct` apply to every shape.
#
# delonix stack apply -f examples/full-volume.yaml --dry-run
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: nas-creds
spec:
# Credentials for the network shares below (cleartext only because this is an example).
stringData:
password: change-me
apiKey: dummy-truenas-api-key
---
# 1. Local named volume: data survives `container rm`.
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: local-data
labels: # stored on the volume; also carries the stack ownership stamp
app: demo
annotations:
delonix.io/note: "plain local volume"
spec:
driver: local # local (default) | nfs — omit for local
quota: 2G # size cap (monitored when rootless, hard cap as root)
alertPct: 80 # usage % above which `ls`/`describe` show WARN (default 90)
---
# 2. Raw driver form: mount an NFS export without the friendly block.
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: raw-nfs
spec:
driver: nfs
device: "10.0.0.5:/mnt/pool/raw" # "server:/export"; only with driver: nfs
mountOptions: "vers=4.1,soft" # `mount -o` options (alias: options)
---
# 3a. NFS block.
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: nfs-media
spec:
nfs:
server: 10.0.0.5 # NAS host or IP (required)
share: /mnt/pool/media # export path (required)
readOnly: false # true = mounted read-only
mountOptions: "vers=4.1,soft" # extra `mount -o` options
quota: 500G
alertPct: 85
---
# 3b. SMB/CIFS block, credentials from a Secret.
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: cifs-backups
spec:
cifs:
server: nas.local # required
share: backups # SMB share name, no leading slash (required)
username: delonix # SMB user
passwordSecret: nas-creds # reads key `password` from this Secret (preferred)
readOnly: false
mountOptions: "vers=3.0"
---
# 3c. SMB/CIFS block with an inline password (discouraged; use passwordSecret instead).
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: cifs-inline
spec:
cifs:
server: nas.local
share: scratch
username: delonix
password: change-me # inline alternative to passwordSecret
readOnly: true
---
# 3d. WebDAV block (Nextcloud/ownCloud).
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: webdav-cloud
spec:
webdav:
server: cloud.example.com # host, or a full URL to override the scheme
share: /remote.php/dav/files/delonix # path inside the WebDAV endpoint
username: delonix
passwordSecret: nas-creds
readOnly: false
---
# 4. Share: an isolated, individually quota'd slice of another volume (`nfs-media` above).
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: team-share
namespace: teamA # scopes the name and data dir; omit = unscoped root
spec:
share:
from: nfs-media # required: the parent volume to carve out of
quota: 5G # soft quota for this slice
alertPct: 90
---
# 5. Provision on a TrueNAS appliance: creates dataset + quota + owner + NFS export,
# then mounts what the appliance reported. Authenticate with an API key OR an account.
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: truenas-dados
spec:
provision:
truenas:
url: https://192.168.122.83 # required
apiKeySecret: nas-creds # Secret with key `apiKey` (or `token`); preferred over an account
insecureTLS: true # a stock TrueNAS serves a self-signed certificate
dataset: tank/dados # required: <pool>/<name>
quota: 2G # quota ON THE NAS (min 1 GiB)
owner:
uid: 1000 # required inside owner
gid: 1000 # required inside owner
mode: "0770" # octal, quoted
share: # drop this block to provision without exporting
networks: ["192.168.122.0/24"] # CIDRs allowed to mount (must be non-empty)
readOnly: false
maprootUser: root
maprootGroup: root
---
# 5b. TrueNAS with account credentials instead of an API key.
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: truenas-account
spec:
provision:
truenas:
url: https://192.168.122.83
username: truenas_admin # account name
passwordSecret: nas-creds # Secret with key `password` (or use inline `password:`)
insecureTLS: true
dataset: tank/accountVolume com bloco de rede
Um volume de REDE montado de um NAS (TrueNAS/Synology/Samba/Nextcloud), estilo PersistentVolume do k8s — o bloco nfs:/cifs:/webdav: de um kind: Volume. A password vem do cofre (passwordSecret); montar precisa de CAP_SYS_ADMIN. O kind: Storage ainda carrega, reescrito nisto com aviso de depreciação — descreviam a mesma montagem de duas maneiras e aterravam no mesmo store.
A NETWORK volume mounted from a NAS (TrueNAS/Synology/Samba/Nextcloud), k8s PersistentVolume-style — the nfs:/cifs:/webdav: block of a kind: Volume. The password comes from the vault (passwordSecret); mounting needs CAP_SYS_ADMIN. kind: Storage still loads, rewritten into this with a deprecation warning — the two described the same mount two ways and landed in the same store.
# Network shares mounted as volumes, like Kubernetes PersistentVolumes.
#
# A share on a NAS (TrueNAS, Synology, Nextcloud, a plain NFS/Samba box) becomes
# a named volume that any container mounts with `-v <name>:/path`.
#
# These are `kind: Volume` with a network-share BLOCK (`nfs:`/`cifs:`/`webdav:`).
# `kind: Storage` was REMOVED — this is what to write instead. The load
# deprecation warning — but the two described the same mount two ways and landed
# in the same store, and nothing said which to use. The block's NAME is the type,
# so a type can no longer contradict its own declaration.
#
# delonix stack apply -f examples/storage.yaml
# delonix container run -v media:/srv/media alpine ls /srv/media
#
# Each field below is shown with a sensible value; delete what you don't need.
# ── NFS (TrueNAS "Unix (NFS) Shares", or any nfs-kernel-server) ───────────────
# ---------------------------------------------------------------------------
# As credenciais do NAS. Ficam num `kind: Secret` (cofre cifrado) e NUNCA em
# texto no manifesto do storage — é essa a razão de existir do `passwordSecret`.
# ---------------------------------------------------------------------------
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: nas-creds
stringData:
password: troca-me
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: cloud-creds
stringData:
password: troca-me
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: media
spec:
nfs:
server: 10.0.0.5 # NAS host or IP
share: /mnt/pool/media # the NFS export path
readOnly: false # true → mounted read-only
mountOptions: "vers=4.1,soft" # extra `mount -o` options (optional)
---
# ── SMB / CIFS (Samba, Windows shares, TrueNAS "Windows (SMB) Shares") ────────
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: backups
spec:
cifs:
server: nas.local
share: backups # the SMB share name (no leading slash)
username: delonix # SMB credentials
# Prefer a secret over an inline password (it won't leak into shell history):
# delonix secret create nas-creds --from-literal password=s3cr3t
passwordSecret: nas-creds # reads key `password` from this secret
# password: s3cr3t # ...or inline (discouraged)
readOnly: false
mountOptions: "vers=3.0"
---
# ── WebDAV (Nextcloud / ownCloud) ─────────────────────────────────────────────
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: cloud
spec:
webdav:
server: cloud.example.com # host, or a full URL (http://host:port) to override the scheme
share: /remote.php/dav/files/delonix # the path within the WebDAV endpoint
username: delonix
passwordSecret: cloud-creds
readOnly: false
Image
Pré-puxa (ou constrói) uma imagem antes dos containers que dependem dela. As imagens VM douradas não têm Kind próprio — geram-se por delonix image vm build.
Pre-pulls (or builds) an image before the containers that depend on it. Golden VM images have no Kind of their own — they're built with delonix image vm build.
# Image — ensure an image is present: either pull it, or build it. Mutually exclusive.
# Apply with: delonix image apply -f examples/image.yaml
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: app
spec:
pull: alpine:3.19 # pull this reference (idempotent)
pullSecret: null # `kind: Secret` with the registry credentials, for a private one
# (full example: examples/image-private-registry.yaml)
---
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: app-built
spec:
build:
tag: app:dev # required — the tag to produce
context: . # build context (default ".")
file: null # Delonixfile/Dockerfile path (default: ./Delonixfile then ./Dockerfile)
Todas as possibilidades — examples/full-image.yamlEvery option — examples/full-image.yaml
# Complete reference for `kind: Image` — ensure an image is present, either by PULLING it
# or by BUILDING it (`pull` and `build` are mutually exclusive: one document each).
#
# delonix stack apply -f examples/full-image.yaml --dry-run
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: registry-cred
spec:
# Registry credentials (`username` + `password`) for a private registry.
stringData:
username: demo
password: change-me
---
# 1. Pull a public image (idempotent).
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: alpine
spec:
pull: alpine:3.19
---
# 2. Pull from a PRIVATE registry, credentials named by the manifest so it also works on a
# host where nobody ran `delonix image login`. Only meaningful together with `pull`.
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: private-app
spec:
pull: ghcr.io/example/private-app:1.0
pullSecret: registry-cred
---
# 3. Build from a Delonixfile/Dockerfile with every build option.
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: app-built
spec:
build:
tag: app:dev # required: the tag to produce
context: . # build context directory (default ".")
file: Delonixfile # Delonixfile/Dockerfile path (default: ./Delonixfile, then ./Dockerfile)
target: runtime # multi-stage: build up to this stage (name or index)
platform: linux/amd64 # target architecture, like the CLI's --platform
noCache: true # bypass the layer cache, like --no-cache
buildArgs: # ARG overrides KEY=VALUE (only for ARGs the file declares)
- VERSION=1.2.3
- CHANNEL=stable
secrets: # build-time secrets, `id=<name>,src=<path>`; used with
- id=npmrc,src=./npmrc.txt # RUN --mount=type=secret,id=npmrc — never lands in a layerVirtualMachine
Uma microVM declarativa (Cloud Hypervisor ou libvirt), com cloud-init por instância. É a camada que o delonix cluster kubeadm usa para provisionar nós.
A declarative microVM (Cloud Hypervisor or libvirt), with per-instance cloud-init. It's the layer delonix cluster kubeadm uses to provision nodes.
# Vm — a declarative microVM (Cloud Hypervisor or libvirt). Apply with:
# delonix vm apply -f examples/vm.yaml
# The golden VM image is built once with: delonix image vm build --name k8s-golden --k8s-version 1.34
#
# Fields are grouped by concern (resources/network/boot/cloudInit/libvirt) so
# a spec this size stays readable. The OLD flat form (every field at the top
# level, no groups) still works exactly as before on any existing manifest —
# both shapes are accepted, this is just the one the docs/examples show.
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: node1
spec:
disk: k8s-golden # required — base qcow2/raw (an overlay is made per VM)
restartPolicy: null # no | on-failure | always
resources:
vcpus: 2
memory: 2G # "2G" | "1024M" (a "…i" suffix is accepted, k8s-style)
hugepages: false
cpuAffinity: null # pin vCPUs, e.g. "8-15"
network:
name: node1-net # ingress network for the VM's tap
mode: null # libvirt only: user | nat | bridge
bridge: null # host bridge / libvirt network
staticIp: null # libvirt nat mode: DHCP reservation on the libvirt network
# Boot mechanism: firmware (cloud images, the default) OR a direct kernel — pick one.
boot:
firmware: null # UEFI firmware path (typical for cloud images)
kernel: null # direct kernel boot (alternative to firmware)
initrd: null # initramfs for direct kernel boot
cmdline: null # kernel cmdline for direct boot
# cloud-init — full parity with `vm create`; a NoCloud seed ISO is auto-generated
# from hostname/sshKeys/userData unless you hand it a prebuilt one via `seed`.
cloudInit:
seed: null # path to a prebuilt NoCloud ISO (overrides the fields below)
hostname: null # hostname applied on first boot
sshKeys: [] # authorized public keys: "ssh-ed25519 AAAA…" or "@/path"
userData: null # your own cloud-init user-data (path/@path) — replaces the generated one
# Advanced libvirt knobs (libvirt backend only): full XML parity, all optional.
libvirt:
backend: null # cloud-hypervisor | libvirt | null (auto)
machine: null # machine type (default q35)
cpuModel: null # host-passthrough (default) | host-model | a named model
cpuTopology: null # { sockets: 1, cores: 4, threads: 2 }
tpm: false # add an emulated TPM 2.0
video: null # virtio | qxl | vga | none
bootOrder: [] # e.g. [cdrom, hd]
extraDisks: [] # [{ source: /data/d.qcow2, bus: virtio, format: qcow2, readOnly: false }]
extraNics: [] # [{ type: bridge, source: br0, model: virtio }]
xmlOverlay: [] # raw <device> XML fragments (trusted manifests only)
xml: null # full <domain> XML, used verbatim (ultimate escape hatch)
devices: [] # VFIO PCI passthrough (sysfs paths) — backend-agnostic (CH + libvirt)
volumes: [] # 9p mounts from a Volume/Storage: [{ name: data, target: /data, readOnly: false }]
vnc: false # graphical console (libvirt backend only)
Todas as possibilidades — examples/full-virtualmachine.yamlEvery option — examples/full-virtualmachine.yaml
# kind: VirtualMachine — a declarative VM (libvirt / Cloud Hypervisor / Proxmox backends).
# Every field of `delonix explain VirtualMachine`, in every accepted shape.
#
# delonix stack apply -f examples/full-virtualmachine.yaml --dry-run
# delonix stack validate -f examples/full-virtualmachine.yaml
#
# The base disk is a VM image from the local store (`delonix image vm ls`, e.g. built by
# `delonix image vm build` or pulled with `delonix image vm pull`) or a path to a qcow2/raw.
#
# Documents in this file (variants are alternatives, hence separate documents):
# 1. supporting resources: a Volume (9p share) and a Network
# 2. `full-flat` — FLAT shape, every field the libvirt backend understands
# 3. `full-grouped` — GROUPED shape (resources/network/boot/cloudInit/libvirt)
# 4. `bridge-vm` — netMode: bridge on a host bridge (alternative to nat + static ip)
# 5. `direct-kernel` — direct-kernel boot (kernel/initrd/cmdline) instead of firmware, Cloud Hypervisor
# 6. `firmware-boot` — explicit UEFI firmware, Cloud Hypervisor
# 7. `prebuilt-seed` — a ready-made cloud-init seed ISO instead of the generated one
# 8. `raw-domain` — the full libvirt <domain> XML used verbatim (escape hatch)
# 9. `built-disk` — the disk is BUILT from a VMfile (spec.build) instead of named (spec.disk)
# ---------------------------------------------------------------------------
# 1. Supporting resources
# ---------------------------------------------------------------------------
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: vm-data # shared into the guest over virtio-9p (libvirt backend)
spec:
driver: local
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: vm-net # the SDN network the VM's tap joins (Cloud Hypervisor backend)
spec:
driver: bridge
---
# ---------------------------------------------------------------------------
# 2. FLAT shape — every field at the top level (libvirt backend)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: full-flat
namespace: default # isolation namespace (enforced on the Cloud Hypervisor backend only)
spec:
disk: delonix-vm-base:ubuntu-24.04 # base image: a name in the VM image store, or a path (an overlay is made per VM)
backend: libvirt # cloud-hypervisor | libvirt | proxmox (omit = auto-detect / default-backend)
restartPolicy: always # no | on-failure | always
vcpus: 4 # omitted => the image's own default, else 1
memory: 4G # "512M" | "2G" | "2Gi" (k8s-style suffix accepted)
hugepages: false # back guest RAM with hugepages
cpuAffinity: "0-3" # pin vCPUs to host CPUs
network: vm-net # SDN network name (declared above; `ingress` is the built-in default when omitted)
netMode: nat # libvirt only: user | nat | bridge (`nat` gives an observable IP)
ip: 192.168.122.50 # static IP: DHCP reservation on the libvirt network (nat mode only)
vnc: true # graphical console (libvirt backend only)
devices: # VFIO PCI passthrough, sysfs path (backend-agnostic)
- /sys/bus/pci/devices/0000:01:00.0
# cloud-init: a NoCloud seed ISO is generated from these unless `seed` is given
hostname: full-flat # hostname applied on first boot
sshKeys: # authorized public keys: "ssh-ed25519 AAAA…" or "@/path/to/key.pub"
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleExampleExampleExampleExampleExample admin@example"
userData: "@/etc/delonix/examples/user-data.yaml" # your own cloud-init user-data (path or @path); replaces the generated one
# volumes shared into the guest over virtio-9p (`kind: Volume`, by name)
volumes:
- name: vm-data
mountPath: /mnt/data # where the guest mounts it
readOnly: false
# advanced libvirt knobs
machine: q35 # machine type (default q35)
cpuModel: host-passthrough # host-passthrough (default) | host-model | a named model
cpuTopology: { sockets: 1, cores: 2, threads: 2 }
tpm: true # emulated TPM 2.0
video: virtio # virtio | qxl | vga | none
bootOrder: [hd, cdrom] # OS boot device order
extraDisks: # extra disks beyond the main overlay + seed
- source: /var/lib/libvirt/images/extra.qcow2
device: disk # disk (default) | cdrom
bus: virtio # virtio (default) | sata | scsi | ide
format: qcow2 # qcow2 (default) | raw
readOnly: false
target: vdb # explicit target dev (auto-assigned when omitted)
extraNics: # extra network interfaces beyond the primary one
- type: network # network (a libvirt network) | bridge (host bridge) | user
source: default
model: virtio # virtio (default) | e1000 | …
mac: "52:54:00:12:34:56" # fixed MAC (random when omitted)
libvirtXmlOverlay: # raw <device> fragments injected before </devices> (TRUSTED manifests only)
- "<rng model='virtio'><backend model='random'>/dev/urandom</backend></rng>"
---
# ---------------------------------------------------------------------------
# 3. GROUPED shape — the same knobs, organised by concern
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: full-grouped
spec:
disk: delonix-vm-base:ubuntu-24.04
restartPolicy: on-failure
devices: [] # VFIO PCI passthrough (stays top-level)
volumes: # 9p mounts (stays top-level)
- { name: vm-data, mountPath: /mnt/data, readOnly: true }
vnc: true # stays top-level
resources:
vcpus: 2
memory: 2G
hugepages: false
cpuAffinity: "0-1"
network: # a mapping here (a plain string is the flat form)
name: vm-net
mode: nat # => netMode
staticIp: 192.168.122.51 # => ip (nat mode)
# bridge: br0 # => bridge (host bridge, bridge mode) — see `bridge-vm`
cloudInit:
hostname: full-grouped
sshKeys: ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleExampleExampleExampleExampleExample admin@example"]
userData: "@/etc/delonix/examples/user-data.yaml"
# seed: /path/seed.iso # prebuilt seed — see `prebuilt-seed`
libvirt: # advanced libvirt knobs
backend: libvirt
machine: q35
cpuModel: host-model
cpuTopology: { sockets: 1, cores: 2, threads: 1 }
tpm: false
video: qxl
bootOrder: [hd]
extraDisks:
- { source: /var/lib/libvirt/images/scratch.raw, format: raw, bus: sata }
extraNics:
- { type: bridge, source: br0, model: e1000 }
xmlOverlay: # => libvirtXmlOverlay
- "<memballoon model='virtio'/>"
# xml: "<domain …>" # => libvirtXml — see `raw-domain`
---
# ---------------------------------------------------------------------------
# 4. Bridge mode: the VM attaches to a host bridge (mutually exclusive with a static `ip`, which is nat-only)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: bridge-vm
spec:
disk: delonix-vm-base:ubuntu-24.04
backend: libvirt
netMode: bridge
bridge: br0 # host bridge / libvirt network to attach to
vcpus: 2
memory: 2G
---
# ---------------------------------------------------------------------------
# 5. Direct-kernel boot (alternative to firmware): kernel [+ initrd] [+ cmdline] — Cloud Hypervisor
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: direct-kernel
spec:
disk: delonix-vm-base:fedora-42
backend: cloud-hypervisor
network: vm-net
kernel: /var/lib/delonix/kernels/vmlinuz-6.14.0 # direct kernel boot
initrd: /var/lib/delonix/kernels/initramfs-6.14.0.img
cmdline: "console=ttyS0 root=/dev/vda1 rw" # kernel command line
vcpus: 2
memory: 2G
---
# ---------------------------------------------------------------------------
# 6. Explicit UEFI firmware (the usual way to boot a cloud image) — Cloud Hypervisor
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: firmware-boot
spec:
disk: delonix-vm-base:ubuntu-24.04
backend: cloud-hypervisor
network: vm-net
firmware: /usr/local/share/delonix/CLOUDHV.fd # UEFI firmware path
vcpus: 2
memory: 2G
---
# ---------------------------------------------------------------------------
# 7. A prebuilt cloud-init seed ISO: replaces the generated one (so hostname/sshKeys/userData are not set)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: prebuilt-seed
spec:
disk: delonix-vm-base:ubuntu-24.04
seed: /var/lib/delonix/seeds/prebuilt-seed.iso # NoCloud ISO handed to the guest as-is
---
# ---------------------------------------------------------------------------
# 8. Escape hatch: the whole libvirt <domain> XML, used verbatim (TRUSTED manifests only)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: raw-domain
spec:
disk: delonix-vm-base:ubuntu-24.04
backend: libvirt
libvirtXml: |
<domain type='kvm'>
<name>raw-domain</name>
<memory unit='MiB'>1024</memory>
<vcpu>1</vcpu>
<os><type arch='x86_64' machine='q35'>hvm</type></os>
</domain>
---
# ---------------------------------------------------------------------------
# 9. The disk is BUILT from a VMfile (spec.build) — mutually exclusive with `disk`
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: built-disk
spec:
build:
context: . # build context, relative to THIS manifest's folder (default ".")
file: VMfile # the VMfile (default <context>/VMfile)
tag: built-disk:latest # tag of the produced image, then used as the VM's disk (default <name>:latest)
compress: true # zstd-compress the result (default true)
network: false # give the build network access (default false: reproducible)
vcpus: 2
memory: 2GContainer
A carga do dia a dia. Só image é obrigatório; todos os outros campos têm default. Cobre rede, storage, recursos (cgroup v2), segredos, segurança, devices e limites.
The everyday workload. Only image is required; every other field has a default. Covers networking, storage, resources (cgroup v2), secrets, security, devices and limits.
# Container — the everyday workload. Apply with: delonix container apply -f examples/container.yaml
# Every field below is optional except `image`; the values shown are the defaults.
#
# Fields are grouped by concern (resources/network/security/storage/env/limits)
# so a spec this size stays readable. The OLD flat form (every field at the
# top level, no groups) still works exactly as before on any existing
# manifest — both shapes are accepted, this is just the one the docs/examples
# show. (The k8s-style Pod shape — `spec.containers[]` — is unaffected: it
# already mirrors k8s's own grouping, see examples/pod.yaml.)
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web
spec:
image: nginx:alpine # required — the only mandatory field
command: [] # overrides the image CMD, e.g. ["nginx", "-g", "daemon off;"]
entrypoint: null # override the image ENTRYPOINT ("" clears it)
user: null # UID/GID or name to run as, e.g. "1000:1000"
hostname: null # container hostname (default: the container name)
detach: true # run in the background (a manifest is declarative)
restartPolicy: always # no | on-failure[:max] | always | unless-stopped
addHost: [] # extra /etc/hosts entries, `name:ip` (the `name=ip` form is refused)
resources: # cgroup v2 limits
memory: max # 64M | 2G | max (no cap)
cpus: "1.0" # CPU cores
cpuWeight: null # relative CPU weight under contention (1-10000)
cpuset: null # pin to CPUs, e.g. "0-3"
ioWeight: null # relative I/O weight
network:
name: host # host | none | <a network created by `network create`>
ports: [] # ["8080:80"] — gives the container its own netns + slirp NAT
expose: null # HTTP port to auto-register in the embedded L7 proxy (see kind: Gateway)
alias: [] # DNS aliases on the network
knows: [] # restrict name resolution to these containers (isolation)
rateBps: null # egress rate limit (e.g. "10mbit"), only with a custom network
rateBurst: null # burst for rateBps
security:
privileged: false # all caps, seccomp off — trusted workloads only
readOnly: false # read-only rootfs (writes go to tmpfs/volumes)
capAdd: [] # ["NET_ADMIN"]
capDrop: [] # ["MKNOD"]
securityOpt: [] # ["seccomp=unconfined", "apparmor=<profile>"]
apparmor: null # AppArmor profile
selinux: null # SELinux context
userns: false # force the subuid user namespace (default-on in rootless)
hostPid: false # share the host PID namespace
hostIpc: false # share the host IPC namespace
detect: false # seccomp in log mode — discover the syscalls a workload uses
storage:
volumes: [] # ["data:/var/lib", "/host/path:/inside:ro"]
tmpfs: [] # ["/scratch"]
env:
vars: [] # ["KEY=value"]
files: [] # ["./.env"]
secrets: [] # vault secret names (see `delonix secret`); injected as env
secretFiles: false # true → secrets go to /run/secrets/<name> instead of env
limits:
devices: [] # ["/dev/fuse"]
gpus: null # all | nvidia | dri
ulimit: [] # ["nofile=1024:2048"]
sysctl: [] # ["net.core.somaxconn=1024"]
labels: [] # ["tier=frontend"]
logDriver: null # json | cri
Todas as possibilidades — examples/full-container.yamlEvery option — examples/full-container.yaml
# kind: Container — EVERY field of the flat container spec, in every accepted shape.
#
# delonix stack apply -f examples/full-container.yaml --dry-run
# delonix stack validate -f examples/full-container.yaml
#
# Layout of this file (one document per variant, separated by `---`):
# 1. supporting resources (Network, Secret, Volume) that the containers reference
# 2. `full-flat` — the FLAT shape: every field at the top level of `spec`
# 3. `full-grouped` — the GROUPED shape: resources/network/security/storage/env/limits
# 4. `env-map` — `env` written as a plain `KEY: value` mapping
# 5. `foreground` — `detach: false` (synchronous, like `container run` without -d)
# 6. `minimal` — the only mandatory field (`image`)
#
# The flat and the grouped shape describe the SAME container; they are separate
# documents because they are alternative spellings (a flat key wins over a grouped one
# of the same target if both are present). The k8s-style `spec.containers[]` shape is a
# `kind: Pod` (see full-pod.yaml) — on a `kind: Container` it is a sunset form.
# ---------------------------------------------------------------------------
# 1. Supporting resources
# ---------------------------------------------------------------------------
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: app-net
spec:
driver: bridge
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: app-secret
spec:
stringData:
DB_PASSWORD: change-me-example # injected as env (or /run/secrets/DB_PASSWORD, see secretFiles)
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: app-data
spec:
driver: local
---
# A peer container so `knows:` has something to resolve.
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: peer
spec:
image: busybox:latest
command: ["sleep", "infinity"]
network: app-net
---
# ---------------------------------------------------------------------------
# 2. FLAT shape — every field at the top level
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: full-flat
namespace: default # isolation namespace (containers of other namespaces cannot reach it)
labels: # metadata labels (the engine adds its own delonix.io/* ones)
tier: frontend
annotations:
example.delonix.io/purpose: full-spec-demo
spec:
image: nginx:alpine # REQUIRED — image reference (pulled if missing)
detach: true # run in the background (default true for a manifest)
command: ["nginx", "-g", "daemon off;"] # overrides the image CMD
entrypoint: /docker-entrypoint.sh # overrides the image ENTRYPOINT ("" clears it)
user: "101:101" # uid[:gid] or user name to run as
hostname: full-flat-host # container hostname (default: the container name)
restartPolicy: on-failure:3 # no | on-failure[:max] | always | unless-stopped (legacy key `restart` also accepted)
addHost: # extra /etc/hosts entries, `name:ip`
- legacy-api:10.0.0.9
labels: # container labels, `key=value` (spec-level, distinct from metadata.labels)
- tier=frontend
- team=platform
# --- network ---
network: app-net # host | none | <custom network> (custom => own netns on the SDN)
ports: # publish host:container[/proto]; [hostIp:]host:container and ranges work too
- "127.0.0.1:18080:80"
- "18443:443/tcp"
- "19000-19002:9000-9002"
expose: 80 # auto-register this HTTP port in the L7 proxy (needs a custom network)
networkAlias: # extra DNS names on the custom network
- web
- frontend
knows: # restrict what this container can reach to these peers (directed Dependency)
- peer
netBps: 10mbit # egress rate limit (needs a custom network)
netBurst: 20mbit # burst for netBps
# --- resources (cgroup v2) ---
memory: 256M # 64M | 2G | max
cpus: "0.5" # CPU cores
cpuWeight: "200" # relative CPU weight under contention (1-10000)
cpuset: "0" # pin to CPUs, e.g. "0-3" (needs the cpuset controller delegated)
ioWeight: "100" # relative block-I/O weight
cgroupParent: # shared intermediate cgroup bounding a GROUP of containers together
name: app-tier # single safe path segment
memoryMax: "1073741824" # aggregate memory ceiling (bytes)
cpus: "2" # aggregate CPU cores
pidsMax: "512" # aggregate process ceiling
# --- security ---
readOnly: true # read-only rootfs (writes go to tmpfs/volumes)
capDrop: ["ALL"] # capabilities to drop
capAdd: ["NET_BIND_SERVICE"] # capabilities to add back
securityOpt: # docker-style security options
- no-new-privileges
# apparmor: <profile> # AppArmor profile — see the note at the end of this file
# selinux: <context> # SELinux context — see the note at the end of this file
userns: true # force the subuid user namespace (already default in rootless)
# --- storage ---
volumes: # named volume or bind mount; optional :ro
- app-data:/var/lib/app
- /etc/hostname:/etc/host-name:ro
tmpfs: # in-memory mounts
- /tmp
- /run/cache
# --- environment ---
env: # KEY=value list
- TZ=Africa/Luanda
- LOG_LEVEL=info
secret: # vault secret names, injected as env (or files, below)
- app-secret
secretFiles: false # true => secrets appear as /run/secrets/<key> instead of env
# --- misc limits ---
ulimit:
- nofile=1024:2048
sysctl:
- net.core.somaxconn=1024
logDriver: json # json | cri
---
# ---------------------------------------------------------------------------
# 3. GROUPED shape — the same fields, organised by concern
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: full-grouped
spec:
image: nginx:alpine
detach: true
command: ["nginx", "-g", "daemon off;"]
entrypoint: /docker-entrypoint.sh
user: "101:101"
hostname: full-grouped-host
restartPolicy: always
addHost: ["legacy-api:10.0.0.9"]
labels: ["tier=frontend"]
logDriver: json
resources: # cgroup v2 limits
memory: 256M
cpus: "0.5"
cpuWeight: "200"
cpuset: "0"
ioWeight: "100"
cgroupParent:
name: app-tier-grouped
memoryMax: "1073741824"
cpus: "2"
pidsMax: "512"
network: # a mapping here (a plain string is the flat form)
name: app-net # host | none | <custom network>
ports: ["18081:80"]
expose: 80 # auto-register in the L7 proxy
alias: [web-grouped] # => networkAlias
knows: [peer]
rateBps: 10mbit # => netBps
rateBurst: 20mbit # => netBurst
security:
privileged: false # true => all caps, seccomp off (trusted workloads only)
readOnly: true
capAdd: ["NET_BIND_SERVICE"]
capDrop: ["ALL"]
securityOpt: ["no-new-privileges"]
userns: true
hostPid: false # share the HOST pid namespace
hostIpc: false # share the HOST ipc namespace
detect: false # seccomp in log mode: discover the syscalls a workload uses
storage:
volumes: ["app-data:/var/lib/app"]
tmpfs: ["/tmp"]
env: # grouped env (vars/files/secrets/secretFiles)
vars: ["TZ=Africa/Luanda"]
files: [] # ["./.env"] — env files read at apply time
secrets: [app-secret]
secretFiles: true # secrets as /run/secrets/<key>
limits:
ulimit: ["nofile=1024:2048"]
sysctl: ["net.core.somaxconn=1024"]
devices: ["/dev/null:/dev/fuse-demo"] # extra device nodes
# gpus: nvidia # all | nvidia | dri — see the note at the end of this file
---
# ---------------------------------------------------------------------------
# 4. `env` as a plain KEY: value mapping (what compose/k8s users write)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: env-map
spec:
image: busybox:latest
command: ["sleep", "infinity"]
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: example-only
PORT: 8080 # non-strings are stringified
---
# ---------------------------------------------------------------------------
# 5. Foreground: detach false blocks apply until the process exits (like `container run`)
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: foreground
spec:
image: busybox:latest
command: ["echo", "hello"]
detach: false
restartPolicy: "no"
---
# ---------------------------------------------------------------------------
# 6. Minimal: `image` is the only mandatory field
# ---------------------------------------------------------------------------
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: minimal
spec:
image: alpine:3.20
# Fields of `delonix explain Container` deliberately NOT set above:
# apparmor / selinux host-LSM specific: an unknown AppArmor profile or SELinux context makes the
# runtime refuse to start the container on hosts without it; kept as comments.
# gpus needs a CDI spec generated by `nvidia-ctk` on the host; refused otherwise.
# privileged: true would defeat the hardening shown in full-flat; shown as `false` in the grouped form.
# hostPid/hostIpc: true share HOST namespaces — off on purpose.
# resources.restart `restart` is only the legacy alias of `restartPolicy`.
# (The `explain` rows `limits`, `resources`, `security`, `storage`, `restart` are the group names / the alias.)Pod
Um pod REAL multi-container: N containers a partilhar as namespaces do pod (mesmo schema spec.containers[] do kind: Container, mas com N containers). Partilham netns (mesmo IP, localhost entre si), IPC e UTS (hostname). A namespace de PID (shareProcessNamespace) é follow-up. Gere-se com delonix pod create/ls/describe/rm/logs.
A REAL multi-container pod: N containers sharing the pod's namespaces (the same spec.containers[] schema as kind: Container, but with N containers allowed). They share netns (same IP, reachable via localhost), IPC and UTS (hostname). The PID namespace (shareProcessNamespace) is a follow-up. Managed with delonix pod create/ls/describe/rm/logs.
# kind: Pod — a REAL multi-container pod: N containers sharing the pod's network
# namespace (same IP, localhost between them), like a Kubernetes Pod. Apply with:
# delonix pod create -f examples/pod-multi.yaml (or `stack apply`)
# Manage as a unit: delonix get pods | describe pod web-app | pod logs web-app
# | delete pod web-app
#
# Same k8s-shaped schema as `kind: Container` (spec.containers[]), but here N
# containers are allowed and they share the pod sandbox. The web + sidecar reach
# each other on `localhost` (shared netns). IPC/UTS/PID sharing
# (shareProcessNamespace) are honored by later runtime slices.
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: web-app
namespace: default
spec:
shareProcessNamespace: false # true → containers see each other's processes
containers:
- name: web
image: nginx:latest
ports:
- containerPort: 80
hostPort: 8080 # published on the pod's shared IP
- name: sidecar
image: busybox:latest
# Reaches the web container on localhost — they share the pod's netns.
command: ["sh", "-c", "while true; do wget -qO- http://localhost:80/ >/dev/null 2>&1; sleep 10; done"]
FirewallPolicy (as duas direcções)
Firewall L4 declarativo por direcção (estilo k8s NetworkPolicy). Cada documento é o estado desejado de UMA direcção de um container-alvo — allowlist + default-deny, idempotente. O kind: Egress deixou de existir: é direction: egress nesta mesma política (partilhavam a struct inteira). Duas políticas para o mesmo par (alvo, direcção) são RECUSADAS — a segunda apagaria as regras da primeira com ambas a dizer que correu bem.
Declarative per-direction L4 firewall (k8s NetworkPolicy-style). Each document is the desired state of one direction for one target container — allowlist + default-deny, idempotent.
# kind: NetworkPolicy — firewall L4 declarativo por container (estilo k8s
# NetworkPolicy), com a direcção em `spec.direction`. Cada documento é o ESTADO
# DESEJADO de uma direcção do container-alvo: aplicar substitui as regras dessa
# direcção (allowlist + default-deny), deixando a outra intacta. Aplicado por
# `stack apply` DEPOIS dos containers existirem. Só actua em containers na SDN.
#
# NOTA: `kind: Ingress` já NÃO é firewall — passou a ser o Ingress L7/HTTP estilo
# k8s (host/path→backend, ver examples/ingress.yaml). Para o firewall inbound usa
# `kind: NetworkPolicy` com `direction: ingress` (como abaixo). E `kind: Egress` (removido)
# FUNDIU-SE aqui: é reescrito no load para `direction: egress`, com aviso de
# depreciação — havia uma struct, um validador, um apply e um dataplane para os
# dois, e nenhum modelo conhecido (NetworkPolicy, security groups, NSGs) separa
# entrada de saída em TIPOS diferentes; separa-as num campo.
# ---------------------------------------------------------------------------
# Os recursos que as políticas abaixo referenciam. Estão aqui DE PROPÓSITO: um
# exemplo que nomeia um container inexistente dá `unresolved reference(s)` a
# quem o copia, e copiar é a primeira coisa que se faz com um exemplo.
# ---------------------------------------------------------------------------
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: sdn-demo
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: dbapp
spec:
image: postgres:16-alpine
network: sdn-demo # a firewall L4 só actua em containers na SDN
env:
- POSTGRES_PASSWORD=exemplo
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata:
name: db-inbound
spec:
direction: ingress # ingress (inbound) | egress (outbound)
target: dbapp # nome do container-alvo (deve existir, em rede custom)
defaultPolicy: deny # allow | deny (default: deny — só passa o que estiver listado)
rules:
- proto: tcp # tcp | udp | any (default: any)
port: "5432" # número, intervalo "n-m", ou "*"
from: 10.219.0.0/16 # CIDR de origem (default: qualquer)
action: allow # allow | deny (default: allow)
note: postgres-from-sdn
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata:
name: db-outbound
spec:
direction: egress # o `kind: Egress` foi removido
target: dbapp
defaultPolicy: deny
rules:
- proto: udp
port: "53"
note: dns
- proto: tcp
port: "443"
to: 0.0.0.0/0 # CIDR de destino (default: qualquer)
note: https-egress
HTTPRoute
Reverse-proxy L7/HTTP embutido — routing por Host + prefixo de path para containers backend. TLS termina no proxy (self-signed ou secretRef); reload a quente por SIGHUP.
Built-in L7/HTTP reverse proxy — routing by Host + path prefix to backend containers. TLS terminates at the proxy (self-signed or secretRef); hot reload via SIGHUP.
# kind: HTTPRoute — reverse-proxy L7/HTTP declarativo. NÃO confundir com o
# kind: Ingress deste runtime (que é firewall L4 inbound por-container). Aqui o
# nome segue o Gateway API do Kubernetes: HTTPRoute = roteamento HTTP por
# Host/path para containers backend; Ingress/FirewallPolicy = firewall L4.
#
# O proxy (hyper embutido) corre dentro do netns do holder — alcança os backends
# por IP interno — e publica as portas de entrada no host. TLS termina no proxy.
# Ciclo 100% YAML: sem CLI, sem cert colado à mão (self-signed ou kind: Secret).
#
# Aplicar com: delonix stack apply -f httproute.yaml
# (os containers backend têm de existir — declara-os no mesmo stack).
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: appnet
spec:
driver: bridge # rede custom: dá IP interno aos backends (o proxy alcança-os por IP)
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web
spec:
image: "nginx:1.27-alpine"
network: appnet # numa rede custom, para o proxy o alcançar por IP
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: api
spec:
image: "hashicorp/http-echo:latest"
command: ["-listen=:3000", "-text=hello from api"]
network: appnet
---
apiVersion: gateway.delonix.io/v1alpha1
kind: HTTPRoute
metadata:
name: loja
spec:
entrypoints:
- { port: 80 }
- { port: 443, tls: true }
tls:
mode: selfSigned # selfSigned (default) | secretRef
# secretRef: loja-cert # kind: Secret com as chaves tls.crt / tls.key (PEM)
rules:
- host: loja.exemplo.ao # casa pelo Host: header (omitir = qualquer Host)
paths:
- path: / # prefixo de path
backend: { service: web, port: 80 }
- path: /api # prefixos mais específicos ganham
backend: { service: api, port: 3000 }
Todas as possibilidades — examples/full-httproute.yamlEvery option — examples/full-httproute.yaml
# Complete reference for `kind: HTTPRoute` — embedded L7/HTTP reverse proxy (Gateway API style).
# Routes by Host header and/or path prefix to backend workloads. The proxy runs inside the
# holder netns (it reaches backends by their SDN IP) and publishes the entry ports on the host.
# TLS terminates at the proxy.
#
# ALL HTTPRoute (and Ingress) documents of a manifest are composed into ONE proxy: listeners
# are merged by port (TLS wins on collision), routes are concatenated, and the proxy has a
# SINGLE certificate (no SNI). If any document says `mode: secretRef` that certificate is used
# for every TLS listener; otherwise a self-signed one covering all the hosts is generated.
# Listeners and TLS are fixed at proxy start-up: changing them restarts the proxy (connections
# are cut); changing only rules is a hot reload with the same PID.
#
# spec fields: entrypoints[] (port, tls), tls (mode, secretRef), rules[] (host, paths[] (path, backend (service, port))).
# Backends must be workloads WITH an IP on a custom SDN network (not host/none networking).
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec: { driver: bridge }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: web }
spec:
network: appnet
containers:
- name: web
image: "nginx:1.27-alpine"
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: api }
spec:
network: appnet
containers:
- name: api
image: "hashicorp/http-echo:latest"
command: ["-listen=:3000", "-text=hello from api"]
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: admin }
spec:
network: appnet
containers:
- name: admin
image: "hashicorp/http-echo:latest"
command: ["-listen=:9000", "-text=admin"]
---
# Certificate for the secretRef mode below (PEM under tls.crt / tls.key; tls_crt / tls_key also work).
# Replace the placeholders with a real pair before serving real traffic.
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata: { name: loja-cert }
stringData:
tls.crt: "-----BEGIN CERTIFICATE-----\nplaceholder\n-----END CERTIFICATE-----"
tls.key: "-----BEGIN PRIVATE KEY-----\nplaceholder\n-----END PRIVATE KEY-----"
---
# 1. The main route: two listeners, a certificate from a Secret, host + several path prefixes.
apiVersion: gateway.delonix.io/v1alpha1
kind: HTTPRoute
metadata: { name: loja }
spec:
entrypoints:
- { port: 80 } # plain HTTP listener (tls defaults to false)
- { port: 443, tls: true } # TLS terminates here (requires spec.tls)
tls:
mode: secretRef # selfSigned (default, cert generated for the hosts) | secretRef
secretRef: loja-cert # kind: Secret holding tls.crt / tls.key
rules:
- host: loja.exemplo.ao # matched against the Host header
paths:
- path: / # path prefix; the longest prefix wins
backend: { service: web, port: 80 }
- path: /api
backend: { service: api, port: 3000 }
---
# 2. A second document joins the same proxy: a different host and an extra listener.
apiVersion: gateway.delonix.io/v1alpha1
kind: HTTPRoute
metadata: { name: painel }
spec:
entrypoints:
- { port: 8080 } # extra HTTP listener, merged with the ones above
rules:
- host: admin.exemplo.ao
paths:
- path: /
backend: { service: admin, port: 9000 }
---
# 3. A rule without `host` matches ANY Host (a catch-all by path); `path` omitted defaults to `/`.
# No entrypoints given: defaults to port 80 (plus 443 with TLS if `tls` were set).
apiVersion: gateway.delonix.io/v1alpha1
kind: HTTPRoute
metadata: { name: catch-all }
spec:
rules:
- paths:
- backend: { service: web, port: 80 }Gateway
Expõe UMA porta local à internet pública via pinggy/ngrok/cloudflare — sem conta, sem IP público. Junta-se ao HTTPRoute apontando localPort para onde o proxy L7 escuta: uma URL pública, routing por Host do lado de lá para vários backends.
Exposes ONE local port to the public internet via pinggy/ngrok/cloudflare — no account, no public IP. Pairs with HTTPRoute by pointing localPort at where the L7 proxy listens: one public URL, Host-based routing on the other end to several backends.
# kind: Gateway — exposes ONE local TCP port to the public internet via a
# 3rd-party provider (pinggy/ngrok/cloudflare). Deliberately single-purpose:
# it does the outbound transport only. Point `localPort` at the HTTPRoute
# proxy's own listening port (see httproute.yaml) to combine the two — one
# public URL, routed by Host/path to as many backend containers as you want.
#
# Apply with: delonix net tunnel apply -f tunnel.yaml
# One-shot equivalent, no manifest (pinggy is the default provider):
# delonix net tunnel expose 80
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata:
name: public
spec:
provider: pinggy # pinggy (zero extra binary) | ngrok | cloudflare
localPort: 80 # the HTTPRoute proxy's port, or any container's published port
hostname: null # custom/reserved hostname — ngrok's --url; cloudflare only with a token (informational, the route lives in its dashboard)
token: null # pinggy pro token / ngrok authtoken / a cloudflare NAMED tunnel's token (literal — prefer tokenSecretRef)
tokenSecretRef: null # pull the token from a `kind: Secret`'s `token` key instead
insecureSkipTlsVerify: false # skip TLS verification of the LOCAL backend's self-signed cert — never affects the public URL, no-op for pinggy
Todas as possibilidades — examples/full-gateway.yamlEvery option — examples/full-gateway.yaml
# Complete reference for `kind: Gateway` — exposes ONE local TCP port to the public internet
# through a third-party tunnel provider. It only does the outbound transport; point `localPort`
# at the HTTPRoute proxy's listening port to publish many backends behind one public URL.
#
# spec fields: provider* (pinggy | ngrok | cloudflare), localPort*, hostname, token,
# tokenSecretRef, insecureSkipTlsVerify. (* = required)
# `token` and `tokenSecretRef` are two ways to give the same credential: use ONE per document.
# Only one ngrok tunnel can be alive at a time (its local agent API has a fixed port).
---
# Secret that carries a provider token under the key `token` (placeholder value).
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata: { name: tunnel-token }
stringData:
token: "REPLACE-WITH-PROVIDER-TOKEN"
---
# 1. Minimal: pinggy free tier needs no account and no extra binary.
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata: { name: public-min }
spec:
provider: pinggy
localPort: 80 # local port to expose (e.g. the HTTPRoute proxy, or a published port)
---
# 2. pinggy with a pro token given literally (works, but prefer tokenSecretRef).
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata: { name: public-pinggy-pro }
spec:
provider: pinggy
localPort: 8080
token: "REPLACE-WITH-PINGGY-PRO-TOKEN"
---
# 3. ngrok with a reserved hostname (passed as ngrok's --url) and the token read from a Secret.
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata: { name: public-ngrok }
spec:
provider: ngrok
localPort: 8080
hostname: demo.ngrok.app # custom/reserved hostname (provider-dependent)
tokenSecretRef: tunnel-token # kind: Secret whose `token` key holds the authtoken
---
# 4. cloudflare NAMED tunnel: runs `cloudflared tunnel run --token ...` for a tunnel you already
# created. `hostname` is informational only — the route itself lives in the Cloudflare dashboard.
# `insecureSkipTlsVerify` skips verification of the LOCAL backend's self-signed certificate
# (the origin becomes https://localhost:<localPort>); the public URL is always a real certificate.
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata: { name: public-cloudflare }
spec:
provider: cloudflare
localPort: 8443
hostname: app.example.com
tokenSecretRef: tunnel-token
insecureSkipTlsVerify: true
---
# 5. cloudflare quick tunnel: no token at all (an ephemeral trycloudflare.com URL).
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata: { name: public-cloudflare-quick }
spec:
provider: cloudflare
localPort: 80Volume com bloco share
Uma fatia ISOLADA e com quota própria de outro volume (tipicamente um com bloco de rede) — vários container/vm/pod partilham UM export NFS/CIFS/WebDAV sem se verem. Cada fatia é um subdirectório real do mount pai, registado como o seu próprio volume; consome-se com -v <nome>:/destino, sem nada de novo do lado do consumidor. O kind: ShareVolume deixou de existir: é o MESMO kind: Volume com um bloco share:.
An ISOLATED, individually-quota'd slice of another volume (typically one with a network block) — several container/vm/pod share ONE NFS/CIFS/WebDAV export without seeing each other. Each slice is a real subdirectory of the parent mount, registered as its own volume; consumed with -v <name>:/dest, nothing new on the consumer side. kind: ShareVolume no longer exists: it's the SAME kind: Volume with a share: block.
# A SHARE — an isolated, individually-quota'd slice of an existing volume, so
# several containers/vms/pods can use ONE NAS export without seeing each other's
# data or exhausting each other's quota. A share is a real subdirectory of the
# parent's own mount, registered as its own named volume — consume it exactly
# like any other volume: `-v <name>:/path`.
#
# It is a `kind: Volume` with a `share:` block, not a Kind of its own: a share
# always WAS a volume (that is how `-v` could consume it), and the separate
# `kind: ShareVolume` only added a second record beside it whose one unique
# field was the parent's name. `kind: ShareVolume` was REMOVED: the load refuses
# it and names this form. `storageRef` is still accepted as a spelling of
# `share.from`. What the merge bought: a share is ownable by a stack
# (`delonix.io/stack`), so `--prune` and `destroy` can reach it.
#
# The parent is a `kind: Volume` with a network-share block (`nfs:`/`cifs:`/
# `webdav:`) — see examples/storage.yaml.
#
# `metadata.namespace` (and `--parent`'s `--namespace`) is OPTIONAL, and leaving
# it out means NO owner — the share is written to the unscoped root, exactly
# where a plain `volume create` writes and where `volume ls`/`describe`/`rm`
# read when given no `-n`. It is not shorthand for a namespace called `default`:
# `--namespace default` is a real tenant and a different share.
#
# Apply with: delonix volume apply -f sharevolume.yaml
# Or create a share imperatively, without a manifest at all:
# delonix volume create app-data --parent nas --quota 5G # unowned: `volume ls` shows it
# delonix volume create app-data --parent nas -n teamA # owned: `volume ls -n teamA`
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: nas
spec:
nfs:
server: 10.0.0.5
share: /pool/data
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: app-a
# The namespace scopes the NAME and the data directory: `teamA`'s `app-a` and
# `teamB`'s never share a path, and `-v app-a:/data` in a workload resolves to
# its own namespace's share.
namespace: teamA
spec:
share:
from: nas # required — an existing volume to carve out of
quota: 5G # optional; SOFT quota (measured usage + alert), omit = unlimited
alertPct: 90 # optional; usage % above which ls/describe flag a WARN (default 90)
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: app-b
namespace: teamB
spec:
share:
from: nas # the SAME parent — app-a and app-b never see each other's files
quota: 2G
App
Build por Cloud Native Buildpacks (Paketo/Heroku), sem Dockerfile: aponta para o código e o motor detecta a linguagem, constrói e deixa a imagem no store local.
Build with Cloud Native Buildpacks (Paketo/Heroku), no Dockerfile: point it at the source and the engine detects the language, builds, and leaves the image in the local store.
# kind: App — build por Cloud Native Buildpacks (Paketo/Heroku), sem Dockerfile.
#
# `source` (por omissão ".", relativo ao directório onde correste o comando —
# a mesma convenção do `build.context` de `kind: Image`, não a pasta deste
# manifesto) é detectado automaticamente (`go.mod`, `package.json`,
# `requirements.txt`, ...) para escolher o builder Paketo certo. `builder:
# heroku` troca de família; um builder próprio exige `runImage` explícito —
# este motor não lê o `builder.toml` de um builder alheio para o adivinhar.
#
# Por baixo: um registo OCI descartável (não o loopback, que um container
# builder não alcança) vive numa rede própria desta build, alcançado pelo
# `creator` do lifecycle CNB pelo DNS interno do motor — a mesma rede/DNS que
# qualquer outro container usa para se encontrar, sem dataplane novo. A imagem
# resultante volta para o `ImageStore` local sob o nome de `spec.image`.
#
# Sem cache de build (cada apply reconstrói), a mesma promessa que `kind:
# Image`'s `build:` já faz. Aplicar com `delonix stack apply -f app.yaml`.
---
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata: { name: shop }
spec:
source: .
builder: auto
image: shop:latest
Todas as possibilidades — examples/full-app.yamlEvery option — examples/full-app.yaml
# kind: App — build an application image from source with Cloud Native Buildpacks (no Dockerfile).
#
# delonix stack apply -f examples/full-app.yaml --dry-run
# delonix stack validate -f examples/full-app.yaml
#
# Spec fields: `image` (required), `source`, `builder`, `runImage`. The built image lands in the LOCAL
# image store under the tag in `spec.image`; there is no build cache (every apply rebuilds).
# `builder` selects how the stack is built, and each choice is a separate document below:
# 1. builder: auto — detect the stack (go.mod, package.json, requirements.txt, …), Paketo builder
# 2. builder: heroku — the Heroku builder family
# 3. custom builder — any builder image; `runImage` becomes REQUIRED (and is only read here)
# 4. defaults — only `image`: source ".", builder "auto"
# ---------------------------------------------------------------------------
# 1. Auto-detected stack (Paketo)
# ---------------------------------------------------------------------------
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-auto
labels:
app: shop
spec:
source: . # directory to build; relative to where you RUN the command (not to this file)
builder: auto # auto | heroku | <builder image ref> (default auto)
image: shop-auto:latest # REQUIRED — tag of the built image in the local image store
---
# ---------------------------------------------------------------------------
# 2. Heroku builder family
# ---------------------------------------------------------------------------
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-heroku
spec:
source: .
builder: heroku
image: shop-heroku:latest
---
# ---------------------------------------------------------------------------
# 3. A custom builder image — the engine does not read a custom builder's metadata to discover its run image,
# so `runImage` is mandatory here (omitting it is an error), and meaningless with auto/heroku.
# ---------------------------------------------------------------------------
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-custom
spec:
source: ./services/shop # any directory holding the application source
builder: registry.example.com/buildpacks/builder-jammy-base:latest
runImage: registry.example.com/buildpacks/run-jammy-base:latest
image: shop-custom:1.0.0
---
# ---------------------------------------------------------------------------
# 4. Defaults: only the image tag is mandatory
# ---------------------------------------------------------------------------
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-defaults
spec:
image: shop-defaults:latestService
Um CONJUNTO de containers escolhido por label e publicado como vários registos DNS A sob <nome>.<namespace>.delonix.internal, em round-robin. Sem VIP e sem daemon.
A SET of containers picked by label and published as several DNS A records under <name>.<namespace>.delonix.internal, round-robin. No VIP, no daemon.
# kind: Service (ADR-0032) — um CONJUNTO de containers seleccionado por label,
# publicado como MÚLTIPLOS registos DNS `A` sob
# <nome>.<namespace>.delonix.internal, com ordem rotativa a cada consulta
# (round-robin). Sem VIP, sem dataplane L4 novo, sem daemon.
#
# A membership é recomputada a cada consulta que ultrapassa o TTL do índice DNS
# já existente (`build_dns_index`, ~2s) — um container acrescentado, removido ou
# relabelado aparece dentro de uma janela de TTL, sem precisar de `stack apply`
# outra vez. Um selector vazio (ou que não corresponda a nada) resolve para uma
# lista vazia — nunca é erro, mas o `apply` avisa em voz alta.
#
# `spec.port` é a porta do CONTAINER (o lado que os workloads escutam), nunca
# uma porta de host ou de VIP — o cliente resolve o nome, recebe um IP, e liga
# directamente a essa porta na máquina que calhou.
#
# Aplicar com `delonix stack apply -f service.yaml`.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web-1
labels: { app: web }
spec:
image: "nginx:alpine"
network: appnet
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web-2
labels: { app: web }
spec:
image: "nginx:alpine"
network: appnet
---
apiVersion: networking.delonix.io/v1alpha1
kind: Service
metadata:
name: web
namespace: default
spec:
selector:
matchLabels:
app: web
port: 80
# Um cliente na mesma rede resolve `web.default.delonix.internal` e recebe os
# dois IPs de web-1/web-2, em ordem alternada a cada consulta.
Todas as possibilidades — examples/full-service.yamlEvery option — examples/full-service.yaml
# Complete reference for `kind: Service` (ADR-0032) — a SET of workloads picked by
# label and published as MULTIPLE DNS `A` records under
# <name>.<namespace>.delonix.internal, in rotating order (DNS round-robin).
# No VIP, no L4 dataplane, no daemon. Membership is recomputed within a DNS-index TTL
# (~2s): a workload added, removed or relabelled shows up without re-applying.
#
# spec fields: selector.matchLabels (all listed labels must match — logical AND) and
# port (the CONTAINER-side port; never a host or VIP port). A selector that is empty or
# matches nothing is not an error: the name resolves to an empty set and apply warns.
# The workloads below carry labels through `kind: Workload` (type: container), which
# lowers to a labelled container.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec: { driver: bridge }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: web-1
labels: { app: web, tier: frontend, track: stable }
spec:
type: container
container: { image: "nginx:alpine", network: appnet }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: web-2
labels: { app: web, tier: frontend, track: canary }
spec:
type: container
container: { image: "nginx:alpine", network: appnet }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: api-1
namespace: team-a # a namespace other than `default`
labels: { app: api }
spec:
type: container
container: { image: "caddy:alpine", network: appnet }
---
# 1. Single-label selector, namespace `default`.
# Clients resolve web.default.delonix.internal and get web-1 and web-2 in rotation.
apiVersion: networking.delonix.io/v1alpha1
kind: Service
metadata:
name: web
namespace: default
spec:
selector:
matchLabels:
app: web # every listed label must match
port: 80 # container-side port the members listen on
---
# 2. Multi-label selector: narrows the set to the stable track only (here: web-1).
apiVersion: networking.delonix.io/v1alpha1
kind: Service
metadata:
name: web-stable
spec: # namespace omitted = `default`
selector:
matchLabels:
app: web
track: stable
port: 80
---
# 3. Service living in another namespace: resolves as api.team-a.delonix.internal.
# Namespace isolation applies to DNS: other namespaces get NXDOMAIN (except services in `default`).
apiVersion: networking.delonix.io/v1alpha1
kind: Service
metadata:
name: api
namespace: team-a
spec:
selector:
matchLabels:
app: api
port: 8080NetworkAccessRule
UMA regra de firewall INCREMENTAL por documento: várias regras para o mesmo container acumulam, e cada uma sai sozinha quando o documento sai do manifesto.
ONE INCREMENTAL firewall rule per document: several rules for the same container accumulate, and each one goes away on its own when its document leaves the manifest.
# kind: NetworkAccessRule — UMA regra de firewall INCREMENTAL por documento.
#
# Ao contrário de `kind: FirewallPolicy`/`NetworkPolicy`, que SUBSTITUI o
# estado inteiro de uma direcção a cada apply (e por isso recusa dois
# documentos para o mesmo alvo+direcção), várias NetworkAccessRule para o
# MESMO container acumulam — é o que este exemplo mostra: duas regras
# independentes no mesmo alvo, cada uma retirável sozinha (remove-a do
# manifesto e reaplica; a outra fica intacta).
#
# É o primitivo que faltava para o B4 do plano de reestruturação da CLI
# (ver `docs/adr/0028-network-access-rule-incremental.md`) — `net ingress
# allow`/`net egress allow` já eram incrementais, mas nada declarativo
# conseguia exprimir isso até agora. NÃO substitui `net ingress`/`net
# egress` (essa decisão é trabalho futuro, separado); aplica-se por
# `delonix stack apply -f network-access-rule.yaml`, como qualquer outro
# Kind — não tem verbo de CLI próprio.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec:
driver: bridge
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata: { name: web }
spec:
image: "nginx:alpine"
network: appnet
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: allow-web-from-lan }
spec:
target: web
direction: ingress # ou egress
action: allow # ou deny (default: allow)
proto: tcp # tcp | udp | any (default: any)
port: "8080"
from: "10.0.0.0/24" # CIDR; vazio/omitido = qualquer origem
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: allow-web-metrics-from-monitoring }
spec:
target: web # o MESMO alvo — acumula com a regra acima, não a substitui
direction: ingress
proto: tcp
port: "9090"
from: "10.1.0.0/24"
Todas as possibilidades — examples/full-networkaccessrule.yamlEvery option — examples/full-networkaccessrule.yaml
# Complete reference for `kind: NetworkAccessRule` — ONE firewall rule per document,
# INCREMENTAL: several rules for the same target accumulate (unlike NetworkPolicy, which
# replaces a whole direction). Removing a document from the manifest and re-applying
# with --prune retires only that rule. Applying the same document again replaces only
# the rule it owns ("the last command wins" for the same match).
#
# spec fields: target*, direction*, port*, action, proto, from (* = required).
# The identity of the rule is metadata.name. It only acts on workloads on a custom SDN network.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: appnet }
spec: { driver: bridge }
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata: { name: web }
spec:
network: appnet
containers:
- name: web
image: "nginx:alpine"
---
# 1. Every field explicitly set: allow TCP/8080 into `web` from one CIDR.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-8080-from-lan }
spec:
target: web # workload the rule applies to
direction: ingress # ingress (traffic TO the workload) | egress (traffic FROM it)
action: allow # allow (accept, default) | deny (drop)
proto: tcp # tcp | udp | any (default any)
port: "8080" # a port, a range "n-m", or "*" (any)
from: 10.0.0.0/24 # CIDR of the other end (source on ingress); omit = anywhere
---
# 2. Accumulates with rule 1 on the same target: a second, independent ingress rule.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-metrics-from-monitoring }
spec:
target: web
direction: ingress
proto: tcp
port: "9090"
from: 10.1.0.0/24
---
# 3. Port range, UDP, defaults for action (allow) and from (anywhere).
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-udp-range }
spec:
target: web
direction: ingress
proto: udp
port: "5000-5100"
---
# 4. Explicit deny of one abusive source, any protocol, any port.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-block-bad-host }
spec:
target: web
direction: ingress
action: deny
proto: any
port: "*"
from: 203.0.113.7/32
---
# 5. EGRESS: `from` names the DESTINATION CIDR the workload may reach.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-egress-https }
spec:
target: web
direction: egress
action: allow
proto: tcp
port: "443"
from: 0.0.0.0/0
---
# 6. Minimal form: only the required fields (direction + port + target).
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-minimal }
spec:
target: web
direction: egress
port: "53"RuntimePolicy
O tecto de admissão do PRÓPRIO nó (grupo security.delonix.io): recusar --privileged, rede do host, :latest, registos fora da lista, passthrough de dispositivos numa VM. É o mesmo policy.json que container run e vm create já consultam antes de criar seja o que for. Singleton do nó: um manifesto declara no máximo um. Converge como qualquer Kind, mas tirá-lo do manifesto não o remove — só delonix policy unset o faz.
The node's OWN admission ceiling (group security.delonix.io): refuse --privileged, host networking, :latest, unlisted registries, device passthrough into a VM. It is the same policy.json that container run and vm create already check before creating anything. A node singleton: a manifest declares at most one. It converges like any Kind, but dropping it from the manifest does not remove it — only delonix policy unset does.
# Complete reference for `kind: RuntimePolicy` (M04, `docs/roadmap/
# 13-improvements-traceability.md`) — the node's own admission ceiling, given
# a manifest form. `stack apply` writes/updates `<root>/policy.json`, the SAME
# file `container run`/`vm create` already check before anything is created.
#
# A node-wide SINGLETON: whatever `metadata.name` says, `apply` always writes
# the one `policy.json` — a manifest may declare at most one `RuntimePolicy`
# document (a second one is refused by `stack validate`/`apply`, before either
# is applied).
#
# Converges like any other Kind — a changed field is really applied, and
# `delonix get runtimepolicies` / `delonix describe runtimepolicy <name>` show
# what is in effect. What it does NOT do is come down on its own: removing this
# document from the manifest and running `stack apply --prune` (or `stack
# destroy`) leaves the ceiling exactly as it was — see the module doc of
# `bins/delonix-runtime-bin/src/cmd/policy.rs`. The only way to lower it is
# `delonix policy unset`, which asks for confirmation (or `--force`).
#
# spec fields — every field defaults to "no opinion", so a policy states only
# what it wants to restrict:
# mode `enforce` (refuse, default) or `warn` (allow, and
# report what would have been refused).
# denyPrivileged refuse `container run --privileged`.
# denyHostNetwork refuse `--net host`, this engine's DEFAULT network mode.
# denyLatestTag refuse a container image reference with no tag or `:latest`.
# allowedRegistries only these registries may be pulled from. Empty = no opinion.
# denyDevicePassthrough refuse `vm create --device` (VFIO PCI passthrough) — the
# VM equivalent of `--privileged`, and then some: it gives
# the guest DMA to host hardware.
# denyLatestVmImage refuse a VM disk image reference with no tag or `:latest`.
# Separate from `denyLatestTag` on purpose: a `vm --vmfile`
# build tags its output `<name>:latest` by default.
# allowedImageUrlHosts hosts a `vm create --url-img` qcow2 may be fetched from.
# Empty = no opinion (TLS alone, per the CLI's own `--help`).
#
# `delonix explain RuntimePolicy` prints this from the generated schema.
---
apiVersion: security.delonix.io/v1alpha1
kind: RuntimePolicy
metadata: { name: node-ceiling }
spec:
mode: enforce
denyPrivileged: true
denyHostNetwork: true
denyLatestTag: true
allowedRegistries: [ghcr.io, docker.io]
denyDevicePassthrough: true
denyLatestVmImage: true
allowedImageUrlHosts: [cloud.debian.org]
IPPool
Um livro de reservas de endereços DO HOST que as rotas reclamam: uma rota com spec.pool segura UM endereço do pool enquanto for declarada e responde nesse endereço em vez do loopback. Só IPv4; com announce: local o endereço já tem de estar numa interface deste host. Um pool com um endereço reservado não se remove nem encolhe.
A reservation ledger of HOST addresses that routes claim: a route with spec.pool holds ONE address of the pool for as long as it is declared, and answers on that address instead of loopback. IPv4 only; with announce: local the address must already be on an interface of this host. A pool with a leased address cannot be removed or shrunk.
# Complete reference for `kind: IPPool` (ADR-0046, D3) — a reservation ledger of HOST
# addresses that routes claim. A route with `spec.pool: <name>` holds ONE address of the
# pool for as long as it is declared, is reachable on that address instead of loopback,
# and (with `hosts: [host]`) has its names pointed at it in the operator's /etc/hosts.
#
# spec fields:
# addresses a single address, a range `a.b.c.d-e.f.g.h`, or a CIDR (a CIDR wider than
# /31 leaves out its network and broadcast address). IPv4 only.
# announce `local` (default): the address must ALREADY be on an interface of this
# host — the apply checks it and stops when it is not. `l2` (the engine adds
# it and announces it) is planned and refused.
# interface only with `announce: l2`.
#
# The ledger is observable: `delonix get ippools`, `delonix describe ippools edge`.
# A pool cannot be removed, and its addresses cannot shrink under a lease, while a route
# holds an address — remove or change the route first. 127.0.0.10-12 are used here
# because every 127/8 address is on the loopback and needs no setup.
---
apiVersion: networking.delonix.io/v1alpha1
kind: IPPool
metadata: { name: edge }
spec:
addresses: ["127.0.0.10-127.0.0.12"]
announce: local
NetworkZone
Uma zona da SDN do próprio Proxmox VE e as vnets dentro dela. O documento não nomeia a infraestrutura: realiza-o o provider de zonas configurado (hoje um nó Proxmox registado por DELONIX_PROXMOX_*); sem nenhum, o apply recusa. Sem actualização no lugar: o apply garante as vnets listadas, e o teardown remove as vnets, depois a zona.
A zone of Proxmox VE's own SDN and the vnets inside it. The document never names the infrastructure: the configured zone provider realizes it (today a Proxmox node registered through DELONIX_PROXMOX_*); with none, apply refuses. No update in place: apply ensures the listed vnets, and teardown removes the vnets, then the zone.
# kind: NetworkZone — a zone of Proxmox VE's own SDN and the vnets inside it
# (ADR-0049 addendum). The document never names the infrastructure: it is
# realized by the zone provider the runtime has configured, which today is a
# Proxmox node registered through DELONIX_PROXMOX_URL/_NODE and a credential
# (see "Remote providers" in the README). With no provider configured, `apply`
# refuses; `manifest validate` does not need one.
#
# metadata.name is the zone's SDN id on the node — a lowercase letter and up to
# seven more lowercase letters or digits; the provider checks the exact format.
# Ownership: every vnet's alias ends with this document's owner mark
# (`[delonix-owner:<token>]`), and a zone is this document's only if the engine
# created it (a zone has no text field to mark). A zone or vnet of the same name
# that is not is refused, never adopted, and never deleted. Each apply and each
# teardown is one transaction under the cluster's SDN lock: refused while
# someone else's SDN changes are pending, rolled back if a step fails.
# There is no update in place: `apply` ensures every vnet listed is present, and
# a vnet dropped from the list stays until the whole zone is torn down
# (`stack apply --prune`, `stack destroy`, or `--replace NetworkZone/<name>`),
# which removes the vnets, then the zone, then commits once.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkZone
metadata: { name: lab }
spec:
vnets:
- { name: labnet1, alias: "lab network 1" }
- { name: labnet2 }
NetworkGateway
Aliases e regras de filtro numa firewall de perímetro (ADR-0051), hoje o OPNsense, registado por DELONIX_OPNSENSE_URL e um par key/secret de API. Aliases entram antes das regras e saem depois delas; a identidade de uma regra é a sua description. Uma alteração ao spec é remover e voltar a aplicar.
Aliases and filter rules on a perimeter firewall (ADR-0051), today OPNsense, registered through DELONIX_OPNSENSE_URL and an API key/secret pair. Aliases go in before rules and come out after them; a rule's identity is its description. A spec change is a remove and re-apply.
# kind: NetworkGateway — aliases and filter rules on a perimeter firewall
# appliance (ADR-0051). `spec.provider` names a registered gateway provider:
# `opnsense`, registered through DELONIX_OPNSENSE_URL and a credential — a
# generated API key/secret pair (DELONIX_OPNSENSE_CREDENTIAL names a
# `kind: Secret` with `key` and `secret`); an account's password is refused by
# the appliance. `manifest validate` does not contact it.
#
# Aliases are applied before rules and removed after them, because the
# appliance checks a rule's source against its alias table. A rule is FOUND by
# its `description` and an alias by its `name`, but OWNED only when it carries
# this document's owner mark — a firewall category `delonix-owner:<token>` the
# engine creates and attaches. One with the same name and no mark is refused,
# never adopted, and the teardown leaves it alone. The apply is also refused
# while the appliance has changes someone saved and did not apply: its apply
# pushes everything staged. There is no update in place: a changed spec is
# removed and applied again.
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkGateway
metadata: { name: edge }
spec:
provider: opnsense
aliases:
- name: delonix_web
kind: host # host | network
content: ["10.20.0.10", "10.20.0.11"]
description: web tier
rules:
- description: office to web tier
source: 192.168.1.0/24 # an alias name, a CIDR, or any
destination: delonix_web
protocol: TCP # omit for any protocol