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 → Volume → Storage → ShareVolume → Image → Vm → Container → Pod → Ingress/Egress → Dependency → HTTPRoute → Tunnel).

Semântica garante-presente (idempotente por nome), não um reconciliador: sem diffing, rollout nem rollback — fail-fast, o que já foi aplicado fica. Os templates abaixo são os ficheiros reais em 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.

# 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: delonix.io/v1
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: delonix.io/v1
kind: Secret
metadata:
  name: app-env
spec:
  fromEnvFile: ./app.env      # ex.: DATABASE_URL=..., API_TOKEN=...

Pod

A forma de Pod do Kubernetes (spec.containers[]) para kind: Container — portas/env/resources/securityContext/volumeMounts estruturados. v1 aceita UM container; para vários, kind: Pod (ver examples/pod-multi.yaml).

# kind: Container — Pod-shaped (k8s-like). Apply with:
#   delonix container apply -f examples/pod.yaml
# The familiar Pod schema: spec.containers[] with structured ports/env/resources/
# securityContext + pod-level volumes. v1 accepts ONE container (>1 is an error).
# 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: delonix.io/v1
kind: Network
metadata:
  name: web-net
spec:
  driver: bridge
---
apiVersion: delonix.io/v1
kind: Container
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)
  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

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.

# 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: Vm` 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: delonix.io/v1
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: delonix.io/v1
kind: Workload
metadata:
  name: db
spec:
  type: vm                 # lowers to kind: Vm
  vm:                      # == kind: Vm spec (examples/vm.yaml)
    disk: delonix-vm-k8s:1.34
    vcpus: 2
    memory: 4G
---
apiVersion: delonix.io/v1
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: delonix.io/v1
kind: Workload
metadata:
  name: fast-vm
spec:
  type: microvm            # lowers to kind: Vm, forcing the microVM hypervisor
  microvm:                 # == kind: Vm 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)

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. Compila para firewall L4 por-container, sem dataplane novo.

# 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.
#
# Compila para firewall L4 por-container: no `to`, ingress default-deny (protege) +
# allow do IP do `from`. Várias Dependency para o mesmo `to` acumulam os allow.
# Aplicar com `delonix stack apply -f dependency.yaml`.
---
apiVersion: delonix.io/v1
kind: Network
metadata: { name: appnet }
spec:
  driver: bridge
---
apiVersion: delonix.io/v1
kind: Container
metadata: { name: db }
spec:
  image: "postgres:16"
  network: appnet
  env: { POSTGRES_PASSWORD: dev }
---
apiVersion: delonix.io/v1
kind: Container
metadata: { name: app }
spec:
  image: "alpine:3.19"
  network: appnet
  command: ["sleep", "infinity"]
---
apiVersion: delonix.io/v1
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

FirewallPolicy

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.

# kind: FirewallPolicy — 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: Ingress`/`kind: Egress` continuam a funcionar como alias — 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: delonix.io/v1
kind: Network
metadata:
  name: sdn-demo
spec:
  driver: bridge
---
apiVersion: delonix.io/v1
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: delonix.io/v1
kind: FirewallPolicy
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: delonix.io/v1
kind: FirewallPolicy
metadata:
  name: db-outbound
spec:
  direction: egress          # = kind: Egress
  target: dbapp
  defaultPolicy: deny
  rules:
    - proto: udp
      port: "53"
      note: dns
    - proto: tcp
      port: "443"
      to: 0.0.0.0/0
      note: https

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.

# 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: FirewallPolicy` (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: delonix.io/v1
kind: Network
metadata:
  name: sdn-demo
spec:
  driver: bridge
---
apiVersion: delonix.io/v1
kind: Container
metadata:
  name: web
spec:
  image: nginx:alpine
  network: sdn-demo          # o proxy L7 só alcança backends na SDN
---
apiVersion: delonix.io/v1
kind: Container
metadata:
  name: api
spec:
  image: caddy:alpine
  network: sdn-demo
---
apiVersion: delonix.io/v1
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: delonix.io/v1
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 } }

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.

# kind: Stack — groups a whole app (networks, containers, storage, …) 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 (Secret → Network → Volume
# → Storage → ShareVolume → Image → Vm → Container → Pod → Ingress/Egress →
# Dependency → HTTPRoute → Tunnel). Each child inherits the Stack's namespace
# unless it sets its own. You can still write the resources as separate documents
# — the Stack is just a convenient single place to bundle them.
apiVersion: delonix.io/v1
kind: Stack
metadata:
  name: blog
  namespace: default
spec:
  networks:
    - name: blog-net
      spec:
        driver: bridge
  containers:
    - name: web
      spec:
        image: nginx:latest
        network: blog-net
        ports: ["8080:80"]
        expose: 80
    - name: db
      spec:
        image: postgres:16
        network: blog-net
        env: ["POSTGRES_PASSWORD=change-me"]
  # Every groupable Kind is available: secrets, volumes, storage, shareVolumes,
  # images, vms, containers, pods, ingress, egress, firewallPolicies,
  # httpRoutes, tunnels, dependencies.
  dependencies:
    - name: web-knows-db
      spec:
        from: web
        to: db

Cluster

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

# 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: delonix.io/v1
kind: Cluster
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

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.

# Network — a user-defined network. Containers join it with `--net <name>`.
# Apply with:  delonix network apply -f examples/network.yaml
apiVersion: delonix.io/v1
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)

Volume

Um volume local nomeado — os dados sobrevivem a container rm. Para armazenamento de REDE (NFS/SMB/WebDAV) usa antes kind: Storage.

# Volume — a named local volume (data survives `container rm`). For NETWORK
# storage (NFS/SMB/WebDAV) use `kind: Storage` instead (see examples/storage.yaml).
# Apply with:  delonix volume apply -f examples/volume.yaml
apiVersion: delonix.io/v1
kind: Volume
metadata:
  name: appdata
spec:
  driver: local             # local | nfs (for nfs, prefer kind: Storage)
  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)

Storage

Um volume de REDE montado de um NAS (TrueNAS/Synology/Samba/Nextcloud), estilo PersistentVolume do k8s. A password vem do cofre (--password-secret). Montar precisa de CAP_SYS_ADMIN.

# Storage — network folder volumes mounted 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`.
#
#   delonix storage 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: delonix.io/v1
kind: Secret
metadata:
  name: nas-creds
stringData:
  password: troca-me
---
apiVersion: delonix.io/v1
kind: Secret
metadata:
  name: cloud-creds
stringData:
  password: troca-me
---
apiVersion: delonix.io/v1
kind: Storage
metadata:
  name: media
spec:
  type: nfs                     # nfs | cifs | smb | webdav
  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: delonix.io/v1
kind: Storage
metadata:
  name: backups
spec:
  type: cifs                    # `smb` is an alias for `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: delonix.io/v1
kind: Storage
metadata:
  name: cloud
spec:
  type: 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. Com --vm o mesmo Kind cobre as imagens VM douradas.

# Image — ensure an image is present: either pull it, or build it. Mutually exclusive.
# Apply with:  delonix image apply -f examples/image.yaml
apiVersion: delonix.io/v1
kind: Image
metadata:
  name: app
spec:
  pull: alpine:3.19         # pull this reference (idempotent)
---
apiVersion: delonix.io/v1
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)

Vm

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.

# 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: delonix.io/v1
kind: Vm
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)

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.

# 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: delonix.io/v1
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

  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: Tunnel)
    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

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.

# 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 pod ls | describe web-app | logs web-app | rm 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: delonix.io/v1
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"]

Ingress / Egress

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.

# kind: FirewallPolicy — 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: FirewallPolicy` com `direction: ingress` (como abaixo). `kind: Egress`
# (firewall outbound) continua a funcionar.
# ---------------------------------------------------------------------------
# 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: delonix.io/v1
kind: Network
metadata:
  name: sdn-demo
spec:
  driver: bridge
---
apiVersion: delonix.io/v1
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: delonix.io/v1
kind: FirewallPolicy
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: delonix.io/v1
kind: Egress
metadata:
  name: db-outbound
spec:
  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.

# 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: delonix.io/v1
kind: Network
metadata:
  name: appnet
spec:
  driver: bridge           # rede custom: dá IP interno aos backends (o proxy alcança-os por IP)
---
apiVersion: delonix.io/v1
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: delonix.io/v1
kind: Container
metadata:
  name: api
spec:
  image: "hashicorp/http-echo:latest"
  command: ["-listen=:3000", "-text=hello from api"]
  network: appnet
---
apiVersion: delonix.io/v1
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 }

Tunnel

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.

# kind: Tunnel — 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:
#   delonix net tunnel expose --provider pinggy --local-port 80
apiVersion: delonix.io/v1
kind: Tunnel
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 — provider-dependent, omit for an ephemeral URL
  token: null                  # pinggy pro token / ngrok authtoken (literal — prefer tokenSecretRef)
  tokenSecretRef: null         # pull the token from a `kind: Secret`'s `token` key instead

ShareVolume

Uma fatia ISOLADA e com quota própria de um Storage — 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.

# kind: ShareVolume — an isolated, individually-quota'd slice of an existing
# kind: Storage (NFS/CIFS/WebDAV), so multiple container/vm/pod can share ONE
# NAS export without seeing each other's data or exhausting each other's
# quota. Each ShareVolume is a real subdirectory of the Storage's own mount,
# registered as its own named volume — consume it exactly like any other
# volume: `-v <name>:/path`.
#
# Apply with:  delonix sharevolume apply -f sharevolume.yaml
# (the storageRef below must already exist — create it first, e.g.
#  `delonix storage create nas --type nfs --server 10.0.0.5 --share /pool/data`)
apiVersion: delonix.io/v1
kind: Storage
metadata:
  name: nas
spec:
  type: nfs
  server: 10.0.0.5
  share: /pool/data
---
apiVersion: delonix.io/v1
kind: ShareVolume
metadata:
  name: app-a
spec:
  storageRef: nas             # required — an existing kind: Storage
  quota: 5G                   # optional; SOFT quota (measured usage + alert), omit = unlimited
  alertPct: 90                 # optional; usage % above which ls/describe flag a WARN (default 90)
---
apiVersion: delonix.io/v1
kind: ShareVolume
metadata:
  name: app-b
spec:
  storageRef: nas              # the SAME parent Storage — app-a and app-b never see each other's files
  quota: 2G