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_EXTRA

Pod

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

NetworkRoute

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

NetworkPolicy

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

Ingress

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

Stack

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

Network

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

Volume

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

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

VirtualMachine

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: 2G

Container

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

Volume 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:latest

Service

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

NetworkAccessRule

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