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