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). Os antigos kind: Storage, ShareVolume e Egress foram removidos: a capacidade vive agora em blocos do Volume (nfs:/cifs:/webdav:/share:) e do NetworkPolicy (direction: egress) — aplicá-los pelo nome antigo dá erro, a apontar o substituto. 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 → VirtualMachine → Container → Pod → Ingress → NetworkPolicy → HTTPRoute → Gateway). The former kind: Storage, ShareVolume and Egress were removed: the capability now lives in blocks on Volume (nfs:/cifs:/webdav:/share:) and on NetworkPolicy (direction: egress) — applying them by the old name errors with the replacement. The full list, with the groups and what each document becomes, is in Resource structure.
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.
# ============================================================================
# kind: Secret — saco de pares CHAVE=valor, cifrado em repouso (ADR-0020)
# kind: Secret — a bag of KEY=value pairs, encrypted at rest (ADR-0020)
#
# PT: Um Secret guarda pares chave=valor cifrados em repouso (XChaCha20-Poly1305
# sob a chave-mestra do host). É consumido por `Container.secret` (como env
# ou ficheiros /run/secrets/<nome>), pelos blocos `cifs:`/`webdav:` de
# `kind: Volume` (`passwordSecret`, que lê a chave `password`),
# por `Image.pullSecret` e por `provision.truenas.*Secret`. É a forma
# DECLARATIVA do `delonix secret create`, fechando o ciclo só em YAML.
# EN: A Secret holds key=value pairs encrypted at rest (XChaCha20-Poly1305 under
# the host master key). It is consumed by `Container.secret` (as env or
# /run/secrets/<name> files), by the `cifs:`/`webdav:` blocks of
# `kind: Volume` (`passwordSecret`, which reads the `password` key),
# by `Image.pullSecret` and by `provision.truenas.*Secret`. It is the
# DECLARATIVE form of `delonix secret create`, closing the loop in YAML only.
#
# PT: Três fontes — `stringData`, `fromEnvFile`, `fromEnv` — que podem ser
# COMBINADAS num só documento. Precedência: `fromEnvFile` primeiro, depois
# `stringData` sobrepõe-se (o inline ganha ao ficheiro), e `fromEnv` junta-se.
# Não há campo `data:` (base64): o motor só lê estas três.
# EN: Three sources — `stringData`, `fromEnvFile`, `fromEnv` — that can be
# COMBINED in one document. Precedence: `fromEnvFile` first, then `stringData`
# overrides it (inline wins over the file), and `fromEnv` adds to it. There is
# no `data:` (base64) field: the engine only reads these three.
#
# PT: Aplicar: delonix stack apply -f examples/core/secret.yaml
# EN: Apply: delonix stack apply -f examples/core/secret.yaml
# PT: Os Secrets são aplicados ANTES do que os referencia (Volume/Container/Image).
# EN: Secrets are applied BEFORE whatever references them (Volume/Container/Image).
# ============================================================================
# ── 1. stringData — inline, cómodo para dev ─────────────────────────────────
# stringData — inline, handy for dev
# PT: Os VALORES ficam em CLARO no ficheiro, por isso o apply imprime um aviso.
# Serve para dev; nunca commites valores reais num repositório.
# EN: The VALUES are CLEARTEXT in the file, so the apply prints a warning.
# Fine for dev; never commit real values to a repository.
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: nas-creds
labels: # PT: rótulos livres guardados com o recurso | EN: free-form labels stored with the resource
app: demo
annotations: # PT: anotações livres | EN: free-form annotations
delonix.io/purpose: "example only"
spec:
stringData:
password: change-me # PT: a chave que `Volume.<cifs|webdav>.passwordSecret` procura | EN: the key `Volume.<cifs|webdav>.passwordSecret` looks for
apiKey: dummy-api-key # PT: `apiKeySecret` procura `apiKey` (ou `token`) | EN: `apiKeySecret` fields look for `apiKey` (or `token`)
username: demo # PT: `Image.pullSecret` quer `username` + `password` | EN: `Image.pullSecret` wants `username` + `password`
# ── 2. fromEnvFile — carregar linhas KEY=value de um ficheiro .env ───────────
# fromEnvFile — load KEY=value lines from a .env file
# PT: Mantém os valores FORA do manifesto. Um caminho relativo resolve-se contra
# a pasta do próprio manifesto.
# EN: Keeps the values OUT of the manifest. A relative path resolves against the
# manifest's own folder.
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: app-env
spec:
fromEnvFile: ./secret-source.env # PT: ex.: DATABASE_URL=..., API_TOKEN=... | EN: e.g. DATABASE_URL=..., API_TOKEN=...
# ── 3. fromEnv (mapa) — ler do ambiente do PROCESSO, renomeando ──────────────
# fromEnv (mapping) — read from the PROCESS environment, renaming
# PT: `<chave no secret>: <variável de ambiente a ler>`. A chave é o que o
# consumidor procura; a variável é a que o job de CI exporta. Uma variável
# NÃO definida é um ERRO no apply, nunca um secret vazio.
# EN: `<key in the secret>: <environment variable to read>`. The key is what the
# consumer looks up; the variable is whatever the CI job exports. A variable
# that is NOT set is an ERROR at apply time, never an empty secret.
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: env-map-secret
spec:
fromEnv:
password: EXAMPLE_DB_PASSWORD # PT: `provision.truenas.passwordSecret` quer `password` | EN: `provision.truenas.passwordSecret` wants `password`
token: EXAMPLE_API_TOKEN # PT: `Gateway.tokenSecretRef` quer `token` | EN: `Gateway.tokenSecretRef` wants `token`
# ── 4. fromEnv (lista) — atalho quando chave == variável ─────────────────────
# fromEnv (list) — shorthand when key name == variable name
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: env-list-secret
spec:
fromEnv:
- DATABASE_URL
- SENTRY_DSN
# ── 5. Combinado — as três fontes num só documento ──────────────────────────
# Combined — all three sources in one document
# PT: O ficheiro dá a base, `stringData` sobrepõe `LOG_LEVEL`, `fromEnv` junta um
# valor lido do ambiente do processo.
# EN: The file gives the base, `stringData` overrides `LOG_LEVEL`, `fromEnv` adds a
# value read from the process environment.
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: combined-secret
spec:
fromEnvFile: ./secret-source.env
stringData:
LOG_LEVEL: debug
fromEnv:
- EXAMPLE_EXTRA
Campos de spec — 3spec fields — 3
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
fromEnv | FromEnv | — | — | Keys read from the PROCESS's environment at apply time. `["DB_PASSWORD"]` takes `$DB_PASSWORD` and stores it under that name; `{ password: DB_PASSWORD }` stores it under `password` instead — which is what the consumers of a secret usually want, since `Storage`/`Tunnel`/ `provision` each look for a key by a fixed name. This is the form a CI job has: the value arrives as an environment variable from the runner's own secret store, and there is no file to point at and nothing to write into the manifest. Neither of the other two shapes could express it. |
fromEnvFile | string | — | — | Path to a `KEY=value` file (e.g. `.env`) — keeps the values OUT of the manifest. Applied BEFORE `stringData` (inline overrides the file). |
stringData | object | — | {} | Inline `KEY: value` pairs. **Plaintext in the manifest** — convenient for dev, but the value stays in cleartext in the file; for production prefer `fromEnvFile` (outside version control) or the CLI's `secret create`. Warned at apply. |
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. O MESMO ficheiro mostra também o pod REAL multi-container: N containers a partilhar netns (mesmo IP, localhost entre si), IPC e UTS, geridos com delonix pod create/ls/describe/rm/logs.
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. The SAME file also shows the REAL multi-container pod: N containers sharing netns (same IP, reachable via localhost), IPC and UTS, managed with delonix pod create/ls/describe/rm/logs.
# ============================================================================
# kind: Pod — N contentores que partilham um namespace de rede (ADR-0006)
# kind: Pod — N containers sharing one network namespace (ADR-0006)
#
# PT: Esquema k8s (`spec.containers[]` com ports/env/resources/securityContext
# estruturados + `volumes[]` ao nível do pod) mais um campo `network` próprio
# do motor. Os membros partilham o netns do pod (mesmo IP, `localhost` entre
# si) e os namespaces IPC/UTS; `shareProcessNamespace: true` partilha também
# o PID. Só `containers[].image` é obrigatório.
# EN: k8s schema (`spec.containers[]` with structured ports/env/resources/
# securityContext + pod-level `volumes[]`) plus an engine-specific `network`
# field. Members share the pod netns (same IP, `localhost` between them) and
# the IPC/UTS namespaces; `shareProcessNamespace: true` shares PID too. Only
# `containers[].image` is mandatory.
#
# PT: Aplicar: delonix stack apply -f examples/compute/pod.yaml
# EN: Apply: delonix stack apply -f examples/compute/pod.yaml
# ============================================================================
# ── 1. mínimo — só containers[].image é obrigatório ─────────────────────────
# minimal — only containers[].image is mandatory
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: minimal-pod
spec:
containers:
- image: alpine:3.20 # PT: membro sem nome => contentor "minimal-pod-c0" | EN: unnamed member => container "minimal-pod-c0"
command: ["sleep", "infinity"]
# ── 2. recursos de apoio para os cenários abaixo ────────────────────────────
# supporting resources for the scenarios below
---
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 # PT: referido por `persistentVolumeClaim.claimName` | EN: referenced via `persistentVolumeClaim.claimName`
spec:
driver: local
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: pod-cache # PT: referido por `source` (extensão delonix) | EN: referenced via `source` (delonix extension)
spec:
driver: local
# ── 3. um contentor — esquema k8s com ports/env/volumes/securityContext ─────
# single container — k8s schema with ports/env/volumes/securityContext
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: web
namespace: default
spec:
network: pod-net # PT: host | none | <rede própria> | EN: host | none | <custom network>
restartPolicy: Always # PT: Always | OnFailure | Never (valores delonix também OK) | EN: Always | OnFailure | Never (delonix spellings also OK)
expose: 80 # PT: auto-registar esta porta HTTP no proxy L7 (opcional) | EN: auto-register this HTTP port in the L7 proxy (optional)
hostAliases: # PT: entradas /etc/hosts extra, forma k8s (uma entrada por hostname) | EN: extra /etc/hosts entries, k8s shape
- ip: 10.0.0.9
hostnames: [legacy-api, legacy-api.internal]
containers:
- name: web
image: nginx:latest
command: ["/bin/sh", "-c"] # PT: substitui o ENTRYPOINT (semântica k8s) | EN: overrides ENTRYPOINT (k8s semantics)
args: ["nginx -g 'daemon off;'"] # PT: substitui o CMD | EN: overrides CMD
ports:
- containerPort: 80
hostPort: 8080 # PT: publicado só quando hostPort está posto | EN: 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" # PT: 500m => 0.5 núcleos | EN: 500m => 0.5 cores
memory: 256Mi
securityContext:
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
volumes:
- name: site
hostPath:
path: /srv/www # PT: bind mount de um directório do host | EN: bind mount of a host directory
- name: scratch
emptyDir: {} # PT: efémero => tmpfs | EN: ephemeral => tmpfs
# ── 4. multi-contentor completo — todos os campos de pod e de contentor ──────
# full multi-container — every pod-level and container field
---
apiVersion: compute.delonix.io/v1alpha1
kind: Pod
metadata:
name: full-pod
namespace: default # PT: namespace de isolamento (um pod isola como um contentor) | EN: isolation namespace (a pod is isolated like a container)
labels:
app: full-pod
spec:
# ---- nível do pod / pod-level ----
network: pod-net # PT: extensão delonix: rede SDN do netns partilhado do pod | EN: delonix extension: SDN network of the pod's shared netns
restartPolicy: OnFailure # PT: Always | OnFailure | Never (also always/on-failure/no) | EN: Always | OnFailure | Never (also always/on-failure/no)
hostname: full-pod-host # PT: hostname partilhado por todos os membros (default: nome do pod) | EN: hostname shared by every member (default: pod name)
shareProcessNamespace: false # PT: true => membros veem os processos uns dos outros (ver cenário 5) | EN: true => members see each other's processes (see scenario 5)
hostAliases: # PT: entradas /etc/hosts extra em cada membro, forma k8s | EN: 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]
# ---- volumes do pod, referidos por containers[].volumeMounts[].name ----
volumes:
- name: site
hostPath: # PT: bind mount de um directório do host | EN: bind mount of a host directory
path: /srv/www
- name: scratch
emptyDir: # PT: efémero, apoiado por tmpfs (RAM do host) | EN: ephemeral, backed by tmpfs (host RAM)
medium: Memory # PT: "" avisaria: k8s significa disco do nó, este motor usa sempre tmpfs | EN: "" would warn: k8s means node disk, this engine always uses tmpfs
- name: data
persistentVolumeClaim: # PT: um Volume NOMEADO (`kind: Volume`), por nome | EN: a NAMED volume (`kind: Volume`), by name
claimName: pod-data
- name: cache
source: pod-cache # PT: extensão delonix: Volume nomeado por string `source` | EN: delonix extension: named Volume by plain source string
# ---- membros / members ----
containers:
- name: web # PT: nome do membro; o contentor chama-se <pod>-<nome> (default c<índice>) | EN: member name; container is <pod>-<name> (default c<index>)
image: nginx:latest # PT: OBRIGATÓRIO | EN: REQUIRED
command: ["/bin/sh", "-c"] # PT: `command` k8s => substitui o ENTRYPOINT | EN: k8s `command` => overrides ENTRYPOINT
args: ["nginx -g 'daemon off;'"] # PT: `args` k8s => substitui o CMD | EN: k8s `args` => overrides CMD
workingDir: /usr/share/nginx/html # PT: directório onde o processo arranca | EN: directory the process starts in
ports:
- containerPort: 80
hostPort: 8080 # PT: publicado no host SÓ quando hostPort está posto | EN: published only when hostPort is set
protocol: TCP # PT: TCP | UDP (default TCP) | EN: TCP | UDP (default TCP)
hostIP: 127.0.0.1 # PT: endereço de bind da porta publicada | EN: bind address of the published port
- containerPort: 443 # PT: sem hostPort => só informativo, não publicado | EN: 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: # PT: aplicados como limites cgroup (`requests` são só indicativos) | EN: enforced as cgroup limits (`requests` advisory)
cpu: "500m" # PT: 500m => 0.5 núcleos | EN: 500m => 0.5 cores
memory: 256Mi
securityContext:
privileged: false
runAsUser: 101
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
- name: sidecar # PT: 2º membro: alcança `web` em localhost (netns partilhado) | EN: 2nd 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
# ── 5. PID partilhado — os membros veem os processos uns dos outros ──────────
# shared PID — 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"]
# PT: Campos aceites mas IGNORADOS num `kind: Pod`:
# spec.detach, spec.expose os membros arrancam sempre em segundo plano e não são
# auto-registados no proxy L7 — usa HTTPRoute/Ingress para L7.
# containers[].tty aceite e ignorado (o motor decide o tty por attach/detach).
# resources.requests só indicativo; o motor aplica `limits`.
# EN: Fields accepted but IGNORED on a `kind: Pod`:
# spec.detach, spec.expose members always start detached and are not auto-registered in
# the L7 proxy — use a HTTPRoute/Ingress for L7 exposure.
# containers[].tty accepted and ignored (the engine decides tty by attach/detach).
# resources.requests advisory only; the engine enforces `limits`.
Campos de spec — 9spec fields — 9
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
containers | array<PodContainer> | simyes | — | — |
detach | boolean | — | true | — |
expose | integer | — | — | delonix extension: auto-register an HTTP port in the L7 proxy. |
hostAliases | array<HostAlias> | — | [] | k8s `hostAliases`: extra `/etc/hosts` entries, in the k8s shape (`{ip, hostnames[]}`) rather than docker's `name:ip`. Same effect as `--add-host`; normalized below. Wired on purpose: without it, the SAME `kind: Container` gained or lost the feature depending on which shape of spec was used — flat had it, k8s silently did not. |
hostname | string | — | — | — |
network | string | — | "host" | delonix extension: the SDN network the POD's shared netns attaches to. A `<custom>` name selects that network's bridge. `host`/`none` (the default) mean "the pod's own netns on the default bridge" — a pod IS a shared netns, so it never gets the host's. They are kept as the default because every existing manifest relies on it; only a custom name changes anything. Was parsed and **entirely ignored** until v0.47.0: `create_pod` hardcoded `ingress`, so a pod declared on a custom network landed on the default bridge in silence. |
restartPolicy | string | — | "no" | k8s `restartPolicy`: `Always`|`OnFailure`|`Never` (delonix values also accepted). |
shareProcessNamespace | boolean | — | false | k8s `shareProcessNamespace`: the pod's containers see each other's processes (shared PID namespace). Default `false`, like k8s. Honored by `kind: Pod` (see `cmd::pod`); ignored for a single `kind: Container`. |
volumes | array<PodVolume> | — | [] | — |
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.
# ============================================================================
# kind: Workload — UM objecto declarativo para todo o tipo de compute (ADR-0001/0006)
# kind: Workload — ONE declarative object for every compute type (ADR-0001/0006)
#
# PT: Um Workload é açúcar: NÃO sobrevive ao load — é reescrito no Kind alvo e
# flui pelo apply normal desse Kind. `spec.type` escolhe o alvo e o bloco com
# o nome do type carrega EXACTAMENTE o spec desse Kind (nada é redefinido):
# type: container -> spec.container == kind: Container (compute/container.yaml)
# type: vm -> spec.vm == kind: VirtualMachine (compute/virtualmachine.yaml)
# type: pod -> spec.pod == kind: Pod (compute/pod.yaml)
# type: microvm -> spec.microvm == kind: VirtualMachine, backend FORÇADO a cloud-hypervisor
# Exactamente UM bloco, o que casa com `type` (qualquer outro é erro). O
# `metadata` (name/namespace/labels/annotations) é herdado pelo recurso baixado.
# EN: A Workload is sugar: it does NOT survive the load — it is rewritten into the
# target Kind and flows through that Kind's normal apply. `spec.type` picks
# the target and the block named after the type carries EXACTLY that Kind's
# spec (nothing is redefined):
# type: container -> spec.container == kind: Container (compute/container.yaml)
# type: vm -> spec.vm == kind: VirtualMachine (compute/virtualmachine.yaml)
# type: pod -> spec.pod == kind: Pod (compute/pod.yaml)
# type: microvm -> spec.microvm == kind: VirtualMachine, backend FORCED to cloud-hypervisor
# Exactly ONE block, the one matching `type` (any other is an error). The
# `metadata` (name/namespace/labels/annotations) is inherited by the lowered resource.
#
# PT: Aplicar: delonix stack apply -f examples/compute/workload.yaml
# Ver o que baixa: delonix stack apply --dry-run -f examples/compute/workload.yaml
# EN: Apply: delonix stack apply -f examples/compute/workload.yaml
# See what it lowers to: delonix stack apply --dry-run -f examples/compute/workload.yaml
# ============================================================================
# ── 1. recursos de apoio para os cenários abaixo ────────────────────────────
# supporting resources for the scenarios below
---
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
# ── 2. type: container — o spec de Container plano, todos os campos ──────────
# type: container — the flat Container spec, every field
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-container
namespace: default # PT: herdado pelo Container baixado | EN: inherited by the lowered Container
labels: { tier: backend }
annotations: { example.delonix.io/note: lowers-to-container }
spec:
type: container # PT: container | vm | pod | microvm | EN: container | vm | pod | microvm
container:
image: nginx:alpine # PT: OBRIGATÓRIO | EN: REQUIRED
detach: true
command: ["nginx", "-g", "daemon off;"]
entrypoint: /docker-entrypoint.sh
user: "101:101"
hostname: wl-container-host
restartPolicy: always # PT: default é "no" | EN: default is "no"
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"]
# ── 3. type: container — o spec de Container agrupado ────────────────────────
# type: container — the grouped Container spec
---
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 # PT: no | on-failure[:max] | always | unless-stopped | EN: 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"] }
# ── 4. type: pod — um pod multi-contentor (forma k8s), todos os campos de pod ─
# 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 # PT: rede SDN do netns partilhado do pod | EN: SDN network of the pod's shared netns
restartPolicy: OnFailure # PT: Always | OnFailure | Never | EN: Always | OnFailure | Never
hostname: wl-pod-host
shareProcessNamespace: true # PT: os membros veem os processos uns dos outros | EN: 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"]
# ── 5. type: vm — um spec de VirtualMachine completo (backend libvirt), plano ─
# 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
provider: { type: libvirt } # PT: `vm` aceita qualquer backend (ao contrário de `microvm`) | EN: `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'/>"
# ── 6. type: vm — o spec de VirtualMachine agrupado ──────────────────────────
# type: vm — the grouped VirtualMachine spec
---
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 }
provider: { type: libvirt, spec: { machine: q35 } }
# ── 7. type: microvm — VM no hipervisor microVM; backend FORÇADO a cloud-hypervisor
# type: microvm — a VM on the microVM hypervisor; backend FORCED to cloud-hypervisor
---
apiVersion: compute.delonix.io/v1alpha1
kind: Workload
metadata:
name: wl-microvm
spec:
type: microvm
microvm:
disk: delonix-vm-base:ubuntu-24.04 # PT: precisa de imagem que arranque em CH (não a golden k8s só-libvirt) | EN: needs an image that boots on CH (not the libvirt-only k8s golden)
provider: { type: cloud-hypervisor } # PT: opcional: só o valor cloud-hypervisor é aceite | EN: optional: only the value cloud-hypervisor is accepted
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"]
Campos de spec — 5spec fields — 5
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
container | ContainerSpec | — | — | — |
microvm | VmSpec | — | — | — |
pod | PodSpec | — | — | — |
type | string | — | "" | — |
vm | VmSpec | — | — | — |
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
# kind: Dependency (alias: KnowDepends) — DIRECTED reachability between workloads
#
# PT: Ao contrário de uma Network (bidireccional), abre UM sentido: `from` alcança
# `to`, mas `to` NÃO inicia para `from` — e `to` deixa de ficar exposto a todos
# os outros na rede. Caso clássico: a app conhece a DB, a DB não conhece a app.
# É AÇÚCAR: no load cada Dependency é reescrita para um `kind: NetworkPolicy`
# com `direction: ingress` no `to` (default-deny + um allow por `from`, nomeando
# o peer por WORKLOAD para sobreviver a restarts). Várias Dependency para o
# mesmo `to` acumulam e fundem-se numa só política. Corre
# `delonix stack apply --dry-run` para ver a política real.
# EN: Unlike a Network (bidirectional), it opens ONE direction: `from` reaches `to`,
# but `to` does NOT initiate towards `from` — and `to` stops being exposed to
# everyone else on the network. Classic case: the app knows the DB, the DB does
# not know the app. It is SUGAR: at load each Dependency is rewritten into a
# `kind: NetworkPolicy` with `direction: ingress` on `to` (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.
#
# PT: Campos spec: from (obrigatório, string), to (string ou lista), ports (lista),
# proto. A regra nomeia o peer por workload, não por IP — o endereço de um
# container vem da SDN e muda num restart.
# EN: spec fields: from (required, string), to (string or list), ports (list), proto.
# The rule names the peer by workload, not by IP — a container's address comes
# from the SDN and changes on a restart.
#
# PT: Aplicar: delonix stack apply -f examples/networking/dependency.yaml
# EN: Apply: delonix stack apply -f examples/networking/dependency.yaml
# ============================================================================
# ── 0. Recursos de apoio INLINE — a rede e os workloads ──────────────────────
# Supporting resources INLINE — the network and the workloads
---
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. todos os campos, `to` como um nome único — app alcança db só em TCP/5432
# 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 # PT: quem INICIA (o "conhecedor") | EN: who INITIATES (the one that "knows")
to: db # PT: o alvo protegido | EN: the protected target
ports: ["5432"] # PT: portas de `to` abertas a `from`; vazio/omitido = qualquer | EN: ports of `to` opened to `from`; empty/omitted = any
proto: tcp # PT: tcp | udp | any (default any) | EN: tcp | udp | any (default any)
# ── 2. `to` como LISTA — uma declaração protege dois alvos de uma vez ────────
# `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"] # PT: várias portas | EN: several ports
proto: tcp
# ── 3. acumulação — um segundo `from` para o mesmo `to` (db) ─────────────────
# accumulation — a second `from` for the same `to` (db)
#
# PT: Fundido com o documento 1, db aceita app (5432/tcp) E worker (qualquer porta/proto).
# EN: Merged with document 1, db accepts app (5432/tcp) AND worker (any port, any proto).
---
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: worker-knows-db }
spec:
from: worker
to: db
Campos de spec — 4spec fields — 4
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
from | string | simyes | — | Container/VM that INITIATES the connection (the one that "knows"). Gains access to `to`. |
ports | array<string> | — | [] | Ports of `to` opened to `from` (e.g. `["5432"]`). Empty = any port. |
proto | string | — | — | `tcp`/`udp`/`any` (default `any`). |
to | array<string> | string | — | — | — |
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)
# kind: NetworkRoute — a DIRECTED path between two NETWORKS (ADR-0013 tier B)
#
# PT: As redes são isoladas por omissão. Isto declara que uma PODE alcançar a
# outra — dirigido, como `kind: Dependency` entre containers: `from` inicia e
# o retorno flui (established); `to` NÃO inicia de volta. O spec tem EXACTAMENTE
# dois campos, `from` e `to` — não há `via`. A identidade é o PAR (from->to),
# não o metadata.name; dois documentos para o mesmo par são recusados.
# EN: Networks are isolated by default. This declares that one MAY reach the other
# — directed, like `kind: Dependency` between containers: `from` initiates and
# the return flows (established); `to` does NOT initiate back. The spec has
# EXACTLY two fields, `from` and `to` — there is no `via`. Identity is the PAIR
# (from->to), not metadata.name; two documents for the same pair are refused.
#
# PT: Uma rota diz que o pacote PODE atravessar; nunca que é PERMITIDO. A política
# por-workload (NetworkPolicy/Dependency) continua a decidir. São duas perguntas
# em série — por isso dois Kinds: NetworkRoute (há caminho?) e depois a política
# (é aceite?). `stack apply --prune` fecha rotas tiradas do manifesto.
# EN: A route says the packet MAY cross; never that it is ALLOWED. Per-workload
# policy (NetworkPolicy/Dependency) still decides. Two questions in series — hence
# two Kinds: NetworkRoute (is there a path?) then the policy (is it accepted?).
# `stack apply --prune` closes routes removed from the manifest.
#
# PT: Aplicar: delonix stack apply -f examples/networking/networkroute.yaml
# Imperativo equivalente: delonix network route <from> <to> (e `--rm` para fechar)
# EN: Apply: delonix stack apply -f examples/networking/networkroute.yaml
# Equivalent imperative: delonix network route <from> <to> (and `--rm` to close)
# ============================================================================
# ── 0. Recursos de apoio INLINE — as redes que as rotas ligam ────────────────
# Supporting resources INLINE — the networks the routes connect
---
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. caminho de um sentido — frontend pode iniciar para backend ───────────
# one-way path — frontend may initiate towards backend
#
# PT: O retorno dessas conversas flui; backend NÃO inicia de volta.
# EN: 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 # PT: metadata é livre; só spec.from/spec.to contam | EN: metadata is free-form; only spec.from/spec.to matter
spec:
from: frontend # PT: rede que PODE iniciar | EN: network that may INITIATE
to: backend # PT: rede alcançada | EN: network reached
# ── 2. um segundo par independente — mgmt alcança as outras duas ────────────
# a second independent pair — mgmt reaches 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. um caminho bidireccional são duas rotas (o par inverso, explícito) ────
# a bidirectional path is two explicit routes (the reverse pair)
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkRoute
metadata: { name: backend-to-mgmt }
spec:
from: backend
to: mgmt
# ── 4. a 2ª pergunta — o `api` ACEITA a travessia? (política por-workload) ───
# the 2nd question — does `api` ACCEPT the crossing? (per-workload policy)
#
# PT: Os workloads existem para a pergunta "é permitido?" ter algo a proteger.
# Sem esta Dependency o caminho existe mas o `api` continua a decidir quem aceita.
# EN: The workloads exist so the "is it allowed?" question has something to protect.
# Without this Dependency the path exists but `api` still decides who it accepts.
---
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"]
---
apiVersion: networking.delonix.io/v1alpha1
kind: Dependency
metadata: { name: web-knows-api }
spec:
from: web
to: api
ports: ["8080"]
proto: tcp
Campos de spec — 2spec fields — 2
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
from | string | simyes | — | Network that may INITIATE. Its workloads reach `to`. |
to | string | simyes | — | Network reached. Its workloads do NOT get to reach `from` back — the return traffic of a conversation `from` started flows (established), a new one from this side does not. Same asymmetry `kind: Dependency` gives between containers. |
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 — Ingress L7/HTTP no formato Kubernetes (networking.k8s.io/v1)
# kind: Ingress — Kubernetes-shaped L7/HTTP Ingress (networking.k8s.io/v1)
#
# PT: É a forma k8s (host/path → backend service) do MESMO proxy L7 embutido do
# `kind: HTTPRoute` — Compat→HTTPRoute: compila para o mesmo proxy e compõe-se
# com todo o Ingress/HTTPRoute do manifesto num ÚNICO proxy com UM certificado
# (sem SNI). O antigo `kind: Ingress`-como-firewall L4 desapareceu — o firewall
# L4 é hoje `kind: NetworkPolicy` (ver ../networking/networkpolicy.yaml).
# EN: It is the k8s shape (host/path → backend service) of the SAME embedded L7
# proxy as `kind: HTTPRoute` — Compat→HTTPRoute: it compiles to that proxy and
# is composed with every Ingress/HTTPRoute of the manifest into a SINGLE proxy
# with ONE certificate (no SNI). The old `kind: Ingress`-as-L4-firewall is gone
# — the L4 firewall is now `kind: NetworkPolicy` (see
# ../networking/networkpolicy.yaml).
#
# PT: Os backends TÊM de ser `kind: Container` com IP numa rede SDN custom (não
# --net host/none). Sem efeito (aceites por fidelidade k8s): `ingressClassName`
# (o proxy embutido é a única classe), `tls[].hosts` (reportado como não
# aplicado), `pathType: Exact` (servido como prefixo, com aviso), uma lista
# `tls` com mais de uma entrada (só a primeira conta) e portas nomeadas
# (`port.name` é recusado: usa `port.number`).
# EN: Backends MUST be `kind: Container` with an IP on a custom SDN network (not
# --net host/none). No effect (accepted for k8s fidelity): `ingressClassName`
# (the embedded proxy is the only class), `tls[].hosts` (reported as not
# applied), `pathType: Exact` (served as a prefix, warned), a `tls` list longer
# than one (only the first is used) and named ports (`port.name` is refused:
# use `port.number`).
#
# PT: Aplicar: delonix stack apply -f examples/gateway/ingress.yaml
# EN: Apply: delonix stack apply -f examples/gateway/ingress.yaml
# ============================================================================
# ── 1. Backends — rede SDN + containers que o Ingress nomeia ─────────────────
# Backends — SDN network + containers the Ingress names
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: sdn-demo
spec:
driver: bridge # PT: o proxy L7 só alcança backends na SDN | EN: the L7 proxy only reaches backends on the SDN
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web
spec:
image: "nginx:alpine" # PT: nginx serve em :80 | EN: nginx serves on :80
network: sdn-demo
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: api
spec:
image: "caddy:alpine" # PT: caddy:alpine serve em :80 | EN: caddy:alpine serves on :80
network: sdn-demo
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: docs
spec:
image: "httpd:2.4-alpine" # PT: httpd serve em :80 | EN: httpd serves on :80
network: sdn-demo
# ── 2. secretRef — certificado BYO de um kind: Secret ────────────────────────
# secretRef — BYO certificate from a kind: Secret
---
# PT: Par PEM sob tls_crt / tls_key (tls.crt / tls.key também servem). Substitui
# os placeholders por um par real antes de servir tráfego real.
# EN: PEM pair 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: shop-tls
spec:
stringData:
tls_crt: "-----BEGIN CERTIFICATE-----\nplaceholder\n-----END CERTIFICATE-----"
tls_key: "-----BEGIN PRIVATE KEY-----\nplaceholder\n-----END PRIVATE KEY-----"
# ── 3. Ingress completo — listeners, TLS, dois hosts, paths, defaultBackend ──
# Full Ingress — listeners, TLS, two hosts, paths, defaultBackend
---
apiVersion: gateway.delonix.io/v1alpha1
kind: Ingress
metadata:
name: shop
spec:
ingressClassName: delonix # PT: aceite por fidelidade k8s (o proxy embutido é a única classe) | EN: accepted for k8s fidelity (the embedded proxy is the only class)
entrypoints: # PT: extensão delonix: portas de listener. Omitir = 80 (+443 com tls) | EN: delonix extension: listener ports. Omit = 80 (+443 when tls is set)
- { port: 80 }
- { port: 443, tls: true } # PT: termina TLS nesta porta | EN: terminate TLS on this port
tls: # PT: uma LISTA em k8s; este motor serve um só certificado (sem SNI) | EN: a LIST in k8s; this engine serves a single certificate (no SNI)
- hosts: [shop.example.ao] # PT: aceite mas NÃO aplicado (sem SNI) | EN: accepted but NOT applied (no SNI)
secretName: shop-tls # PT: kind: Secret com tls_crt/tls_key; omitir = self-signed | EN: kind: Secret with tls_crt/tls_key; omit = self-signed
rules:
- host: shop.example.ao # PT: header Host a casar; omitir = qualquer host | EN: Host header to match; omit = any host
http:
paths:
- path: /
pathType: Prefix # PT: casa por prefixo (Exact é servido como prefixo) | EN: matched by prefix (Exact is served as a prefix)
backend:
service:
name: web # PT: nome de container, resolvido para o IP SDN no apply | EN: container name, resolved to its SDN IP at apply
port:
number: 80 # PT: porta do container (portas nomeadas não são suportadas) | EN: container port (named ports are not supported)
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80 # PT: caddy:alpine serve em :80 | EN: caddy:alpine serves on :80
- host: docs.example.ao # PT: um segundo host no mesmo Ingress | EN: a second host in the same Ingress
http:
paths:
- path: / # PT: `path` default é / | EN: `path` defaults to /
backend:
service:
name: docs
port:
number: 80
defaultBackend: # PT: catch-all quando nenhuma regra casa | EN: catch-all when no rule matches
service:
name: web
port:
number: 80
# ── 4. Ingress mínimo — junta-se ao mesmo proxy, sem TLS, sem entrypoints ────
# Minimal Ingress — joins the same proxy, no TLS, no entrypoints
---
# PT: Sem `entrypoints`: default porta 80. Sem `tls`: só serve HTTP.
# EN: No `entrypoints`: defaults to port 80. No `tls`: serves HTTP only.
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: 80
Campos de spec — 5spec fields — 5
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
defaultBackend | IngressBackend | — | — | Catch-all backend when no rule matches (→ a `host: any, path: /` route). |
entrypoints | array<Entrypoint> | — | [] | delonix extension: listener ports. Omit → 80 (+ 443 when `tls` is set). |
ingressClassName | string | — | — | Accepted for k8s fidelity; the embedded proxy is the only ingress class. |
rules | array<IngressRule> | — | [] | — |
tls | array<IngressTls> | — | [] | k8s TLS block (a LIST). v1 uses a SINGLE cert (no SNI) — the first entry wins. |
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 — agregado que agrupa uma app inteira num só documento
# kind: Stack — the aggregate that groups a whole app in one document
#
# PT: Um Stack reúne redes, contentores, volumes, … (à maneira de um
# k8s-Service). Ao carregar, expande-se nos recursos constituintes, que
# seguem o apply normal por-Kind EM ORDEM DE DEPENDÊNCIA (a ordem que
# `delonix stack plan --fields` imprime — a ordem da tabela de Kinds, não uma
# lista mantida aqui). Cada filho herda o namespace do Stack salvo se definir
# o seu, e pode levar `labels:`/`annotations:` ao lado de `name:`. Um item de
# grupo é `{ name, namespace?, labels?, annotations?, spec }`: `spec` é
# exactamente o `spec` do Kind que o grupo representa.
# EN: A Stack bundles networks, containers, volumes, … (k8s-Service-like). At load
# time it expands into its constituent resources, which then flow through the
# normal per-Kind apply IN DEPENDENCY ORDER (the order `delonix stack plan
# --fields` prints — the order of the Kind table, 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:`. A group item is
# `{ name, namespace?, labels?, annotations?, spec }`: `spec` is exactly the
# `spec` of the Kind the group stands for.
#
# PT: Este exemplo usa TODOS os grupos que um Stack aceita (o teste
# `the_stack_example_names_every_group` falha se faltar um), por isso serve de
# referência de como cada um se parece. Para um Kind sozinho com todas as suas
# opções, ver examples/full-<kind>.yaml.
# EN: This example uses EVERY group a 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.
#
# grupo / group Kind referência / reference
# runtimePolicies RuntimePolicy full-runtimepolicy.yaml
# secrets Secret core/secret.yaml
# networks Network full-network.yaml
# networkRoutes NetworkRoute full-networkroute.yaml
# networkZones NetworkZone networkzone.yaml
# storagePools StoragePool full-volume.yaml
# volumes Volume full-volume.yaml
# objectStorages ObjectStorage storage/objectstorage.yaml
# images Image full-image.yaml
# apps App full-app.yaml
# vms VirtualMachine full-virtualmachine.yaml
# systemContainers SystemContainer systemcontainer.yaml
# containers Container full-container.yaml
# pods Pod full-pod.yaml
# services Service full-service.yaml
# ingress Ingress full-ingress.yaml
# firewallPolicies NetworkPolicy full-networkpolicy.yaml
# networkAccessRules NetworkAccessRule full-networkaccessrule.yaml
# networkGateways NetworkGateway networkgateway.yaml
# ipPools IPPool full-ippool.yaml
# httpRoutes HTTPRoute full-httproute.yaml
# gateways Gateway full-gateway.yaml
# gitopsSources GitOpsSource full-gitopssource.yaml
# workloads Workload full-workload.yaml (rebaixado ao carregar / lowered at load)
# dependencies Dependency full-dependency.yaml (rebaixado ao carregar / lowered at load)
#
# PT: `tunnels:` é a grafia antiga de `gateways:` e ainda carrega. Não agrupáveis:
# `Stack` (não encaixa noutro) e `KubernetesCluster` (procedimento remoto por
# SSH, não um recurso deste nó — ver full-kubernetescluster.yaml).
# EN: `tunnels:` is the old spelling of `gateways:` and still loads. Not groupable:
# `Stack` (does not nest) and `KubernetesCluster` (a remote procedure over SSH,
# not a resource of this node — see full-kubernetescluster.yaml).
#
# PT: Aplicar: delonix stack apply -f examples/core/stack.yaml
# EN: Apply: delonix stack apply -f examples/core/stack.yaml
# ============================================================================
# ── 1. O Stack blog — um documento que nomeia todos os grupos ───────────────
# The blog Stack — one document naming every group
---
apiVersion: core.delonix.io/v1alpha1
kind: Stack
metadata:
name: blog
namespace: default
spec:
# PT: O tecto de admissão do nó, antes de tudo: aplica-se antes de cada grupo
# abaixo, no MESMO apply. Baixá-lo nunca é automático — remover este bloco e
# re-aplicar deixa policy.json intacto; `delonix policy unset` é a única descida.
# EN: The node's admission ceiling, first of all: it applies before every other
# group below, in the SAME apply. 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 # PT: obriga tags fixas — nenhuma imagem abaixo usa :latest | EN: forces fixed tags — no image below uses :latest
# PT: Secrets primeiro: tudo abaixo pode referenciá-los. | EN: 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
# PT: Uma rota diz que as duas redes TÊM caminho; as políticas abaixo é que
# decidem o que passa. | EN: 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
# PT: SDN nativa do cluster — realizada pelo NetworkZoneProvider que o RUNTIME
# tiver configurado (hoje: Proxmox, via DELONIX_PROXMOX_URL), nunca nomeado
# aqui. Aplicar sem provider recusa com erro claro (adenda ADR-0049).
# EN: Cluster-native SDN — realized by whichever NetworkZoneProvider the RUNTIME
# has configured (today: Proxmox, via DELONIX_PROXMOX_URL), never named here.
# Applying without a provider refuses with a clear error (ADR-0049 addendum).
networkZones:
- name: dc1
spec:
vnets:
- name: prod
alias: "Production VLAN"
# PT: Um pool de armazenamento que o administrador do nó declarou em
# /etc/delonix/storage-pools.yaml (ADR-0067). O Stack só diz que o motor o
# usa, e quão longe pode ser sobre-alocado; o driver é do administrador.
# EN: A storage pool the node's administrator declared in
# /etc/delonix/storage-pools.yaml (ADR-0067). The Stack only says the engine
# uses it, and how far it may be over-allocated; the driver is the admin's.
storagePools:
- name: fast
spec:
alertPct: 85
volumes:
- name: db-data
spec:
quota: 5G # PT: tecto duro dos dados do volume | EN: hard cap on the volume's data
alertPct: 80 # PT: avisa aos 80% de uso | EN: warn when it is 80% full
# PT: Armazenamento de objectos S3 (ver storage/objectstorage.yaml). `cephRgw`
# provisiona uma identidade S3 no gateway Ceph do nó; `external` aponta para
# um store teu. As credenciais ficam em secret/<nome>-s3.
# EN: S3-compatible object storage (see storage/objectstorage.yaml). `cephRgw`
# provisions an S3 identity on the node's Ceph gateway; `external` points at a
# store you already run. Credentials land in secret/<name>-s3.
objectStorages:
- name: media
spec:
backend: cephRgw
bucket: media
cephRgw:
endpoint: http://rgw.internal:80
images:
- name: alpine-base
spec:
pull: alpine:3.20 # PT: `pull` e `build` são alternativas; ver full-image.yaml | EN: `pull` and `build` are alternatives; see full-image.yaml
# PT: Cloud Native Buildpacks: código entra, imagem sai (ver full-app.yaml).
# EN: Cloud Native Buildpacks: source in, image out (see full-app.yaml).
apps:
- name: shop
spec:
source: .
image: shop:1.0 # PT: com tag, não :latest — a runtimePolicy acima nega :latest | EN: tagged, not :latest — the runtimePolicy above denies :latest
# PT: Um microVM/VM na mesma SDN. Precisa de imagem ou disco de VM no host para aplicar.
# EN: 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
# PT: Um system container: um userland inteiro no provider remoto configurado do
# runtime (hoje: nó Proxmox, via DELONIX_PROXMOX_URL), nunca nomeado aqui. O
# motor puxa a imagem; memória/swap/cores mudam em sítio, o resto recria (ADR-0058).
# EN: 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 } # PT: o que o Service abaixo selecciona | EN: what the Service below selects by
spec:
image: nginx:1.27-alpine # PT: com tag, não :latest | EN: tagged, not :latest
network: blog-net
ports: ["8080:80"]
expose: 80 # PT: auto-registo no proxy L7 como web.<ns>.delonix.internal | EN: 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"]
# PT: Um pod: vários contentores a partilhar UM namespace de rede (localhost entre eles).
# EN: A pod: several containers sharing ONE network namespace (localhost between them).
pods:
- name: worker-pod
spec:
containers:
- name: app
image: nginx:1.27-alpine
- name: sidecar
image: alpine:3.20
command: ["sleep", "infinity"]
# PT: DNS round-robin sobre cada contentor que o selector casa, sob
# web.<ns>.delonix.internal. Sem VIP, sem daemon.
# EN: 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
# PT: Um conjunto de endereços do host que uma rota pode reclamar com `spec.pool`.
# Só `announce: local` é construído: o endereço já tem de estar numa interface.
# EN: 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.
ipPools:
- name: edge
spec:
addresses: ["127.0.0.10-127.0.0.12"]
# PT: Ingress L7 à maneira do Kubernetes: host/path → serviço de backend.
# EN: 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 } }
# PT: Firewall L4 por contentor. Cada item é o estado desejado INTEIRO de uma direcção.
# EN: Per-container L4 firewall. Each item is the WHOLE desired state of one direction.
firewallPolicies:
# PT: (o ingress de `db` é da Dependency abaixo, e duas políticas para o mesmo
# alvo+direcção são recusadas — esta protege `web`.)
# EN: (`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
# PT: UMA regra incremental: ao contrário de uma firewallPolicy, ADICIONA às
# regras do alvo em vez de substituir a direcção, e removê-la remove só a si.
# EN: 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
# PT: Alias+regra de perímetro num GatewayProvider externo (ADR-0051) —
# `provider: opnsense` tem de estar registado (DELONIX_OPNSENSE_URL + credencial)
# antes de `stack apply` chegar aqui.
# EN: A perimeter alias+rule on an external GatewayProvider (ADR-0051) —
# `provider: opnsense` must already be registered (DELONIX_OPNSENSE_URL and a
# credential) 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
# PT: Proxy reverso L7 nativo, mesmo motor que `ingress:` (ambos fundem num só proxy).
# EN: 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 }
# PT: Alcançabilidade dirigida: `web` pode chegar a `db`, `db` não é exposto aos outros.
# EN: 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
# PT: Um Workload é rebaixado ao carregar para o container/pod/VM que o `type` nomeia.
# EN: 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"]
# PT: Túnel público para um porto local (kind: Gateway). pinggy não precisa de conta.
# (`tunnels:` é a grafia antiga deste grupo e ainda carrega.)
# EN: Public tunnel to a local port (kind: Gateway). pinggy needs no account.
# (`tunnels:` is the old spelling of this group and still loads.)
gateways:
- name: blog-public
spec:
provider: pinggy
localPort: 8080
# PT: Uma fonte de pull (kind: GitOpsSource, ADR-0021): um repositório de onde
# este stack reconcilia. `sync.automated: false` regista-o sem timer;
# `delonix gitops reconcile web-infra` faz uma passagem à mão.
# EN: A pull source (kind: GitOpsSource, ADR-0021): a repository this stack
# reconciles from. `sync.automated: false` registers it without a timer;
# `delonix gitops reconcile web-infra` runs one pass by hand.
gitopsSources:
- name: web-infra
spec:
source:
url: file:///srv/git/web-infra.git
ref: refs/heads/main
path: environments/production
Campos de spec — 26spec fields — 26
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
apps | array<object> | — | — | — |
containers | array<object> | — | — | — |
dependencies | array<object> | — | — | — |
firewallPolicies | array<object> | — | — | — |
gateways | array<object> | — | — | — |
gitopsSources | array<object> | — | — | — |
httpRoutes | array<object> | — | — | — |
images | array<object> | — | — | — |
ingress | array<object> | — | — | — |
ipPools | array<object> | — | — | — |
networkAccessRules | array<object> | — | — | — |
networkGateways | array<object> | — | — | — |
networkRoutes | array<object> | — | — | — |
networkZones | array<object> | — | — | — |
networks | array<object> | — | — | — |
objectStorages | array<object> | — | — | — |
pods | array<object> | — | — | — |
runtimePolicies | array<object> | — | — | — |
secrets | array<object> | — | — | — |
services | array<object> | — | — | — |
storagePools | array<object> | — | — | — |
systemContainers | array<object> | — | — | — |
tunnels | array<object> | — | — | — |
vms | array<object> | — | — | — |
volumes | array<object> | — | — | — |
workloads | array<object> | — | — | — |
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).
# ============================================================================
# kind: KubernetesCluster — um cluster kubeadm em três modos (um por documento)
# kind: KubernetesCluster — a kubeadm cluster in three modes (one per document)
#
# PT: `spec.mode` é o discriminador e cada modo lê só o seu bloco:
# kind → nós em containers, NESTA máquina (sem Docker, sem o binário kind)
# vm → cada nó é uma microVM real da imagem dourada (isolamento de hipervisor)
# ssh → hosts remotos que JÁ EXISTEM (bare-metal ou VMs de outrem)
# Os campos comuns (k8sVersion/podSubnet/serviceSubnet/cni) são partilhados;
# os específicos vivem no bloco do modo (`kind:`, `vm:`, `ssh:`).
# EN: `spec.mode` is the discriminator and each mode reads only its own block:
# kind → nodes as containers, on THIS machine (no Docker, no `kind` binary)
# vm → every node is a real microVM from the golden image (hypervisor isolation)
# ssh → remote hosts that ALREADY EXIST (bare metal or someone else's VMs)
# The common fields (k8sVersion/podSubnet/serviceSubnet/cni) are shared; the
# specific ones live in the mode's block (`kind:`, `vm:`, `ssh:`).
#
# PT: FORA do ciclo do stack. Um cluster NÃO é um recurso do nó — é um
# procedimento remoto por SSH. Por isso este Kind NÃO entra em
# `stack apply`/`--prune`/`destroy`. Aplica-se com o seu próprio comando:
# delonix cluster apply -f examples/cluster/kubernetescluster.yaml
# e valida-se offline (sem tocar em máquina nenhuma) com:
# delonix stack validate -f examples/cluster/kubernetescluster.yaml
# delonix manifest validate -f examples/cluster/kubernetescluster.yaml
# EN: OUTSIDE the stack cycle. A cluster is NOT a node-local resource — it is a
# remote procedure over SSH. So this Kind does NOT take part in
# `stack apply`/`--prune`/`destroy`. Apply it with its own command:
# delonix cluster apply -f examples/cluster/kubernetescluster.yaml
# and validate it offline (touching no machine) with:
# delonix stack validate -f examples/cluster/kubernetescluster.yaml
# delonix manifest validate -f examples/cluster/kubernetescluster.yaml
# ============================================================================
# ── 1. modo kind — cluster local em containers, aqui mesmo ──────────────────
# mode kind — a local cluster in containers, right here
---
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: kind-lab
spec:
mode: kind
k8sVersion: "1.34" # PT: minor do Kubernetes | EN: Kubernetes minor version
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: default # PT: default = a CNI da imagem do nó (kindnet); none = não instala nenhuma | EN: default = the node image's CNI (kindnet); none = install none
controlPlane:
replicas: 1 # PT: >1 exige controlPlaneEndpoint (LB/VIP) | EN: >1 requires controlPlaneEndpoint (LB/VIP)
workers:
replicas: 1
kind: # PT: só lido em mode: kind | EN: only read in mode: kind
image: kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a # PT: fixada por digest — uma tag móvel tornaria o cluster irreprodutível | EN: pinned by digest — a moving tag would make the cluster irreproducible
apiServerPort: 6443 # PT: porta do HOST para o apiserver (o kubeconfig gerado aponta para cá) | EN: host port for the apiserver (the generated kubeconfig points here)
# ── 2. modo ssh — hosts remotos já existentes, control-plane HA ─────────────
# mode ssh — remote hosts that already exist, HA control plane
---
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: ssh-prod
spec:
mode: ssh
k8sVersion: "1.34"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: none # PT: em produção costumas instalar a TUA CNI (Cilium, Calico…) | EN: in production you usually install YOUR own CNI (Cilium, Calico…)
controlPlaneEndpoint: "k8s-api.example.com:6443" # PT: OBRIGATÓRIO com >1 control-plane (o delonix não provisiona o LB; aponta para um que tenhas) | EN: REQUIRED with >1 control-plane (delonix does not provision the LB; point at one you have)
controlPlane:
hosts: # PT: `hosts` (não `replicas`): as máquinas já existem | EN: `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: # PT: só lido em mode: ssh | EN: only read in mode: ssh
user: delonix # PT: precisa de sudo NOPASSWD nos hosts | EN: needs passwordless sudo on the hosts
keyPath: ~/.ssh/id_ed25519 # PT: nome canónico (`key` é a grafia legada) | EN: canonical name (`key` is the legacy spelling)
port: 22
etcd:
mode: stacked # PT: etcd nos control-planes (o default do kubeadm) | EN: etcd on the control-planes (the kubeadm default)
# ── 3. modo vm — cada nó é uma microVM da imagem dourada ────────────────────
# mode vm — every node is a microVM from the golden image
---
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: vm-lab
spec:
mode: vm
k8sVersion: "1.34"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: none # PT: traz a tua CNI; `default` aqui serve UM nó só (recusado acima de um, DX-6701) — para mais nós usa um perfil (doc 4) | EN: bring your own CNI; `default` here serves ONE node only (refused above one, DX-6701) — for more nodes use a profile (doc 4)
controlPlane:
replicas: 1
workers:
replicas: 0
vm: # PT: só lido em mode: vm | EN: only read in mode: vm
image: k8s-golden # PT: tag em `delonix vm image ls`; omitir = a única imagem local, se houver exactamente uma | EN: tag from `delonix vm image ls`; omit = the only local image, if there is exactly one
network: lab-net # PT: criada antes com `delonix network create` | EN: created beforehand with `delonix network create`
vcpus: 2 # PT: por nó | EN: per node
memory: 2G # PT: por nó | EN: per node
sshKey: ~/.ssh/id_ed25519 # PT: chave privada para falar com as VMs; omitir = gera um par ed25519 em <root>/clusters/<name> | EN: private key to reach the VMs; omit = a key is generated under <root>/clusters/<name>
bootTimeout: 300s # PT: espera por SSH depois do arranque | EN: wait for SSH after boot
# ── 4. perfil CNI Cilium (modo vm) — a rede de pods instalada pelo bootstrap ─
# Cilium CNI profile (mode vm) — the pod network the bootstrap installs
---
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: cilium-lab
spec:
mode: vm
k8sVersion: "1.36"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
cni: cilium # PT: um PERFIL (cilium/calico/flannel) fixado a versão + digest, instalado e esperado pelo bootstrap; `delonix cluster network profiles` imprime o estado de cada um | EN: a PROFILE (cilium/calico/flannel) pinned to a version + digest, installed and waited for by the bootstrap; `delonix cluster network profiles` prints the state of each
network: # PT: só lido quando `cni` nomeia um perfil que o lê (em default/none é RECUSADO, DX-1702) | EN: only read when `cni` names a profile that reads it (on default/none it is REFUSED, DX-1702)
dataplane: vxlan # PT: encapsulamento; omitir = vxlan (o único que nada pede à rede entre nós) | EN: encapsulation; omit = vxlan (the only mode that asks nothing of the network between nodes)
kubeProxy: iptables # PT: iptables (default, o modo medido); ipvs; none (só Cilium) muda o `kubeadm init` e não se liga depois | EN: iptables (default, the measured mode); ipvs; none (Cilium only) changes `kubeadm init` and cannot be turned on later
experimental: true # PT: reconhecimento EXPLÍCITO de que o perfil é experimental nesta release; sem ele o manifesto é recusado (DX-1705) | EN: an EXPLICIT acknowledgement that the profile is experimental in this release; without it the manifest is refused (DX-1705)
# mtu: 1450 # PT: MTU do pod; omitir é recomendado (calculado de nodeMtu menos o overhead) | EN: pod MTU; omitting is recommended (computed from nodeMtu minus the overhead)
# nodeMtu: 1500 # PT: o MTU real dos nós, quando não é 1500 | EN: the nodes' real MTU, when it is not 1500
# hubble: true # PT: observabilidade do Cilium (mais pods, mais recursos) | EN: Cilium's observability (more pods, more resources)
# multus: false # PT: interfaces extra — uma EXTENSÃO, nunca a rede primária | EN: extra interfaces — an EXTENSION, never the primary network
# offline: true # PT: instala só do cache local (`cluster network fetch`) | EN: install only from the local cache (`cluster network fetch`)
# interface: ens3 # PT: quando a autodetecção da interface é ambígua | EN: when interface autodetection is ambiguous
controlPlane:
replicas: 1
workers:
replicas: 2 # PT: possível agora — o perfil encaminha pods entre nós | EN: possible now — the profile routes pods between nodes
vm:
image: k8s-golden
network: lab-net
vcpus: 2
memory: 4G # PT: o agente Cilium pede mais do que uma bridge | EN: the Cilium agent asks for more than a bridge does
bootTimeout: 300s
# ── 5. modo ssh com etcd EXTERNO dedicado (CA própria, nº ímpar de membros) ──
# mode ssh with a dedicated EXTERNAL etcd cluster (own CA, odd member count)
---
apiVersion: infrastructure.delonix.io/v1alpha1
kind: KubernetesCluster
metadata:
name: ssh-etcd-prod
spec:
mode: ssh # PT: etcd.mode external é suportado SÓ com mode: ssh | EN: etcd.mode external is supported ONLY with mode: ssh
k8sVersion: "1.34"
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
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
port: 22
etcd:
mode: external # PT: cluster etcd dedicado, criado e gerido pelo delonix | EN: a dedicated etcd cluster, created and managed by delonix
hosts: # PT: hosts etcd dedicados, que já têm de existir; nº ÍMPAR (1 = só dev/test, sem HA; 3, 5, … em produção) | EN: dedicated etcd hosts, which must already exist; ODD count (1 = dev/test only, no HA; 3, 5, … in production)
- address: 10.0.2.11
- address: 10.0.2.12
- address: 10.0.2.13
Campos de spec — 13spec fields — 13
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
cni | enum(default|none|cilium|calico|…) | — | "default" | The pod network. `default` = the engine's own: kindnet in `mode: kind`, a node-local bridge in `mode: vm`/`ssh`, which only serves a ONE-node cluster (more nodes are refused before anything is created); `none` = install nothing, and every node stays `NotReady` until you apply your own CNI; `cilium`/`calico`/`flannel` = a selectable profile, pinned to a version and an artefact digest, installed and waited for by the bootstrap itself (see `cmd::net_profiles`). The accepted set is carried into the published JSON Schema as an `enum`, not only enforced at runtime: before this, `cni` was a plain `"type": "string"` there, so an editor validated `cni: cilum` as fine and the operator found out from DX-1701 after writing the manifest. Kept in step with `net_profiles::CNI_CHOICES` by `the_published_schema_offers_the_same_cni_values_as_the_code`. |
controlPlane | NodesSpec | — | — | — |
controlPlaneEndpoint | string | — | — | — |
etcd | EtcdSpec | — | — | — |
k8sVersion | string | — | — | — |
kind | KindModeSpec | — | — | — |
mode | string | — | "kind" | **The discriminator**: `kind` (containers here), `vm` (golden VMs) or `ssh` (remote hosts already live). One Kind, three paths — the common fields (k8sVersion/podSubnet/cni) are shared, the specific ones live in the mode's block. |
network | NetworkBlock | — | — | How the chosen pod network is configured. Only read when `cni` names a profile that reads it — a knob set on `cni: default`/`none` is REFUSED rather than ignored (DX-1702), because a dropped network setting is indistinguishable from one that was honoured until the traffic fails. |
podSubnet | string | — | "10.244.0.0/16" | — |
serviceSubnet | string | — | "10.96.0.0/12" | — |
ssh | SshSpec | — | — | — |
vm | VmModeSpec | — | — | — |
workers | NodesSpec | — | — | — |
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. O MESMO Kind absorve também a zona de SDN do próprio Proxmox VE (o kind: NetworkZone — sunset: ainda aceite no load mas sem secção própria, a baixar para Network): realizada pelo provider de zonas configurado (DELONIX_PROXMOX_*).
A user network. Containers join it with --net <name>; VMs with network:. bridge is the only driver containers can attach to today. The SAME Kind also absorbs a Proxmox VE SDN zone (the kind: NetworkZone — sunset: still accepted on load but with no section of its own, lowering into Network): realized by the configured zone provider (DELONIX_PROXMOX_*).
# ============================================================================
# kind: Network — uma rede definida pelo utilizador; os containers entram com `--net <nome>`
# kind: Network — a user-defined network; containers join it with `--net <name>`
#
# PT: Um `Network` nativo é um plano L2/L3 local do nó, escolhido por `driver`:
# `bridge` (default, uma bridge local), `overlay` (VXLAN entre nós, opcional
# cifrado por WireGuard), `macvlan`/`ipvlan` (endereço directo no segmento da
# NIC pai). O MESMO kind descreve ainda um segmento realizado por um provider
# de cluster (Proxmox/SDN) — nesse caso leva `spec.provider` e nenhum campo
# nativo; o documento nunca nomeia o endpoint da infra (vem do provider que o
# runtime tem configurado).
# EN: A native `Network` is a node-local L2/L3 plane chosen by `driver`: `bridge`
# (default, a local bridge), `overlay` (VXLAN between nodes, optionally
# WireGuard-encrypted), `macvlan`/`ipvlan` (an address directly on the parent
# NIC's segment). The SAME kind also describes a segment realized by a cluster
# provider (Proxmox/SDN) — then it carries `spec.provider` and no native field;
# the document never names the infra endpoint (it comes from the provider the
# runtime has configured).
#
# PT: Campos spec nativos: driver, subnet, gateway, parent, vni, peers, wgIp
# (alias wg_ip). Nenhum campo é inventado — são os de NETWORK_SPEC_FIELDS.
# EN: Native spec fields: driver, subnet, gateway, parent, vni, peers, wgIp
# (alias wg_ip). No field is invented — these are NETWORK_SPEC_FIELDS.
#
# PT: Aplicar: delonix stack apply -f examples/networking/network.yaml
# EN: Apply: delonix stack apply -f examples/networking/network.yaml
# ============================================================================
# ── 1. bridge — todos os campos relevantes de bridge, explícitos ─────────────
# bridge — every bridge-relevant field spelled out
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-bridge-full
labels:
tier: backend # PT: labels livres (as marcas de posse são postas pelo apply) | EN: free-form labels (ownership stamps are added by apply)
annotations:
example.io/purpose: "all bridge fields"
spec:
driver: bridge # PT: bridge (default) | macvlan | ipvlan | overlay | EN: bridge (default) | macvlan | ipvlan | overlay
subnet: 10.201.0.0/16 # PT: CIDR da rede; omitir = escolhe um /16 livre | EN: CIDR of the network; omit to auto-pick a free /16
gateway: 10.201.0.1 # PT: endereço do gateway; omitir = .1 do subnet | EN: gateway address; omit to default to .1 of the subnet
# ── 2. bridge por defeito — tudo omitido, driver=bridge, subnet auto ─────────
# bridge default — everything omitted, driver=bridge, subnet auto-picked
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-bridge-default
spec: {} # PT: driver cai em bridge, subnet é escolhido | EN: driver defaults to bridge, subnet is auto-picked
# ── 3. overlay (VXLAN) entre nós — vni + peers, em claro ─────────────────────
# 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 # PT: identificador da rede VXLAN | EN: VXLAN network identifier
peers: # PT: outros nós do overlay, pelo IP de underlay | EN: other nodes of the overlay, by underlay IP
- 192.168.1.11
- 192.168.1.12
# ── 4. overlay cifrado (transporte WireGuard) — peers "<ip>=<wg_pubkey>=<wg_ip>"
# encrypted overlay (WireGuard transport) — peers "<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 # PT: IP do túnel WireGuard deste nó (alias: wg_ip) | EN: this node's WireGuard tunnel IP (alias: wg_ip)
peers:
- "192.168.1.11=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==10.250.0.2"
# ── 5. macvlan — o container ganha endereço no segmento L2 da NIC pai ────────
# macvlan — the container gets an address on the parent NIC's L2 segment
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: net-macvlan
spec:
driver: macvlan
parent: eth0 # PT: NIC do host onde o macvlan assenta | EN: host NIC the macvlan sits on
subnet: 192.168.50.0/24
gateway: 192.168.50.1
# ── 6. ipvlan — como macvlan, mas todos partilham o MAC da NIC pai ───────────
# ipvlan — like macvlan, but all 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
# ── 7. provider Proxmox/SDN — segmento realizado por um provider de cluster ──
# provider Proxmox/SDN — segment realized by a cluster provider
#
# PT: NÃO é `kind: NetworkZone` — é `kind: Network` com `spec.provider` (ADR-0070,
# ADR-0071). Tudo fora de `provider` é neutro; `provider.spec` é tipado pelo
# provider que `provider.type` nomeia, e uma chave que ele não tenha é recusada.
# metadata.name é o id do vnet na SDN. `zone` é partilhada pelos seus vnets
# (criada com o primeiro, removida com o último); o que `dns` diz tem de ser
# igual em todas as redes da zona. Sem provider configurado, `apply` recusa;
# `stack validate` não precisa de um.
# EN: NOT `kind: NetworkZone` — it is `kind: Network` with `spec.provider` (ADR-0070,
# ADR-0071). Everything outside `provider` is neutral; `provider.spec` is typed
# for the provider `provider.type` names, and a key it does not have is refused.
# metadata.name is the vnet's SDN id. `zone` is shared by its vnets (created with
# the first, removed with the last); whatever `dns` says must be identical on
# every network of that zone. With no provider configured `apply` refuses;
# `stack validate` does not need one.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: labnet1 }
spec:
subnet: 10.80.0.0/24
gateway: 10.80.0.1
provider:
type: proxmox # PT: o provider que realiza o segmento | EN: the provider that realizes the segment
spec:
zone: lab # PT: a zona SDN em que o vnet vive | EN: the SDN zone this vnet lives in
alias: "lab network 1" # PT: rótulo do vnet na SDN | EN: the vnet's SDN alias
dhcpRange: [{ start: 10.80.0.100, end: 10.80.0.150 }] # PT: gama DHCP do vnet | EN: the vnet's DHCP range
reservations:
- { ip: 10.80.0.20, mac: "BC:24:11:00:00:20" } # PT: reserva por MAC literal | EN: reservation by literal MAC
- { ip: 10.80.0.21, workload: "VirtualMachine/web-1" } # PT: ou por workload VM (o MAC resolve-se depois do VM existir) | EN: or by VM workload (MAC resolved once the VM exists)
dns: # PT: por zona (todas as redes da zona têm de concordar) | EN: per zone (all networks of the zone must agree)
server: pdns # PT: controlador DNS registado no cluster (o motor nunca o cria) | EN: DNS controller registered on the cluster (the engine never creates one)
zone: lab.example # PT: a zona DNS onde os registos A/PTR são escritos | EN: the DNS zone the A/PTR records are written to
reverseServer: pdns # PT: controlador dos PTR; opcional | EN: controller for the PTR records; optional
ipam: pve # PT: controlador IPAM da zona; `pve` quando omitido | EN: the zone's IPAM controller; `pve` when omitted
---
# PT: Segunda rede da MESMA zona `lab` — forma mínima: só o provider e a zona.
# EN: Second network of the SAME `lab` zone — minimal form: just the provider and zone.
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata: { name: labnet2 }
spec:
provider:
type: proxmox
spec: { zone: lab }
Campos de spec — 9spec fields — 9
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
driver | string | — | "bridge" | — |
gateway | string | — | "" | — |
parent | string | — | — | — |
peers | array<string> | — | [] | — |
provider | object | — | — | — |
subnet | string | — | — | — |
vni | integer | — | — | — |
wgIp | string | — | — | Canonical `wgIp` (camelCase, uniform with the rest of the schema); `wg_ip` is still accepted (backward compat). |
wg_ip | object | — | — | — |
Volume
Um volume local nomeado — os dados sobrevivem a container rm. O MESMO Kind cobre armazenamento de REDE (NFS/SMB/WebDAV, com um bloco nfs:/cifs:/webdav: — o antigo kind: Storage, hoje removido, escrito directamente como este bloco) e fatias ISOLADAS com quota própria (bloco share: — o antigo kind: ShareVolume, também removido, vários container/vm/pod a partilhar um export sem se verem). O mesmo ficheiro mostra os três cenários.
A named local volume — the data survives container rm. The SAME Kind covers NETWORK storage (NFS/SMB/WebDAV, with an nfs:/cifs:/webdav: block — the former kind: Storage, now removed and written directly as this block) and ISOLATED, individually-quota'd slices (a share: block — the former kind: ShareVolume, also removed, several container/vm/pod sharing one export without seeing each other). The one file shows all three scenarios.
# ============================================================================
# kind: Volume — volume com nome (os dados sobrevivem a `container rm`)
# kind: Volume — a named volume (data survives `container rm`)
#
# PT: UM só Kind, VÁRIAS formas MUTUAMENTE EXCLUSIVAS (por isso um documento
# cada): um volume local, um driver de rede cru, um bloco de partilha de
# rede (`nfs:`/`cifs:`/`webdav:` — o NOME do bloco É o tipo), uma fatia de
# OUTRO volume (`share:`), aprovisionamento numa NAS (`provision:`) e um
# volume alocado numa storage pool (`pool:`). `quota` e `alertPct` aplicam-se
# a todas as formas. `kind: Storage` e `kind: ShareVolume` foram ABSORVIDOS
# aqui (o load recusa-os e nomeia esta forma); `storageRef` sobrevive como
# alias de `share.from` e `options` como alias de `mountOptions`.
# EN: ONE Kind, SEVERAL MUTUALLY EXCLUSIVE shapes (hence one document each): a
# local volume, a raw network driver, a network-share block
# (`nfs:`/`cifs:`/`webdav:` — the block's NAME is the type), a slice of
# ANOTHER volume (`share:`), provisioning on a NAS (`provision:`) and a
# volume allocated in a storage pool (`pool:`). `quota` and `alertPct` apply
# to every shape. `kind: Storage` and `kind: ShareVolume` were FOLDED in here
# (the load refuses them and names this form); `storageRef` survives as an
# alias of `share.from`, and `options` as an alias of `mountOptions`.
#
# PT: Um workload consome qualquer volume da MESMA forma: `-v <nome>:/caminho`
# (container), `volumes:` (pod/stack), ou `spec.volumes` numa VM (9p libvirt).
# EN: A workload consumes any volume the SAME way: `-v <name>:/path` (container),
# `volumes:` (pod/stack), or `spec.volumes` on a VM (libvirt 9p).
#
# PT: Aplicar: delonix stack apply -f examples/storage/volume.yaml
# EN: Apply: delonix stack apply -f examples/storage/volume.yaml
# ============================================================================
# ── Secret de apoio — credenciais das partilhas de rede abaixo ───────────────
# Supporting Secret — credentials for the network shares below
# PT: As credenciais vivem num `kind: Secret` (cofre cifrado) e NUNCA em texto no
# manifesto — é a razão de existir do `passwordSecret`. Texto só aqui porque
# é um exemplo (o apply avisa-te disso).
# EN: Credentials live in a `kind: Secret` (encrypted vault), NEVER in cleartext
# in the manifest — that is the reason `passwordSecret` exists. Cleartext
# only here because this is an example (the apply warns you about it).
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: nas-creds
spec:
stringData:
password: troca-me # PT: senha SMB/WebDAV | EN: SMB/WebDAV password
apiKey: dummy-truenas-key # PT: chave de API TrueNAS | EN: TrueNAS API key
# ── 1. local — volume local simples (o caso comum) ──────────────────────────
# local — a plain local volume (the common case)
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: local-data
labels: # PT: ficam no volume; carregam também o carimbo de posse do stack | EN: stored on the volume; also carry the stack ownership stamp
app: demo
spec:
driver: local # PT: local (default) — omite para local | EN: local (default) — omit for local
quota: 2G # PT: tecto de tamanho (monitorizado em rootless, rígido como root) | EN: size cap (monitored in rootless, hard as root)
alertPct: 80 # PT: % de uso acima da qual `ls`/`describe` mostram WARN (default 90) | EN: usage % above which `ls`/`describe` show WARN (default 90)
# ── 2. driver cru — montar um export NFS sem o bloco amigável ────────────────
# raw driver — mount an NFS export without the friendly block
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: raw-nfs
spec:
driver: nfs # PT: driver não-local | EN: non-local driver
device: "10.0.0.5:/mnt/pool/raw" # PT: "server:/export"; só com driver: nfs | EN: "server:/export"; only with driver: nfs
mountOptions: "vers=4.1,soft" # PT: opções de `mount -o` (alias: options) | EN: `mount -o` options (alias: options)
# ── 3. nfs: — partilha NFS (TrueNAS, Synology, nfs-kernel-server) ────────────
# nfs: — an NFS share (TrueNAS, Synology, nfs-kernel-server)
# PT: Este é o volume que uma VM consome por `spec.volumes` (9p libvirt) — ver
# examples/compute/virtualmachine.yaml.
# EN: This is the volume a VM consumes via `spec.volumes` (libvirt 9p) — see
# examples/compute/virtualmachine.yaml.
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: nfs-media
spec:
nfs:
server: 10.0.0.5 # PT: host ou IP da NAS (obrigatório) | EN: NAS host or IP (required)
share: /mnt/pool/media # PT: caminho do export NFS (obrigatório) | EN: NFS export path (required)
readOnly: false # PT: true = montado só-leitura | EN: true = mounted read-only
mountOptions: "vers=4.1,soft" # PT: opções extra de `mount -o` | EN: extra `mount -o` options
quota: 500G
alertPct: 85
# ── 4. cifs: — partilha SMB/CIFS (Samba, Windows, TrueNAS SMB) ───────────────
# cifs: — an SMB/CIFS share (Samba, Windows, TrueNAS SMB)
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: cifs-backups
spec:
cifs:
server: nas.local # PT: obrigatório | EN: required
share: backups # PT: nome da partilha SMB, sem barra inicial (obrigatório) | EN: SMB share name, no leading slash (required)
username: delonix # PT: utilizador SMB | EN: SMB user
passwordSecret: nas-creds # PT: lê a chave `password` deste Secret (preferido) | EN: reads key `password` from this Secret (preferred)
# password: troca-me # PT: alternativa inline (desencorajada) | EN: inline alternative (discouraged)
readOnly: false
mountOptions: "vers=3.0"
# ── 5. webdav: — partilha WebDAV (Nextcloud, ownCloud) ───────────────────────
# webdav: — a WebDAV share (Nextcloud, ownCloud)
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: webdav-cloud
spec:
webdav:
server: cloud.example.com # PT: host, ou URL completo (http://host:port) para forçar o esquema | EN: host, or full URL (http://host:port) to override the scheme
share: /remote.php/dav/files/delonix # PT: caminho dentro do endpoint WebDAV | EN: path inside the WebDAV endpoint
username: delonix
passwordSecret: nas-creds
readOnly: false
# ── 6. share: — fatia isolada e com quota própria de OUTRO volume ────────────
# share: — an isolated, individually-quota'd slice of ANOTHER volume
# PT: Um subdirectório real do mount do volume-pai (`nfs-media` acima),
# registado como volume próprio: vários workloads partilham UM export de NAS
# sem verem os dados nem esgotarem a quota uns dos outros. `metadata.namespace`
# é OPCIONAL — ausente = raiz sem dono (não é o tenant `default`). Exclusivo
# com nfs/cifs/webdav, device, driver não-local e provision.
# EN: A real subdirectory of the parent's mount (`nfs-media` above), registered
# as its own volume: several workloads share ONE NAS export without seeing
# each other's data or exhausting each other's quota. `metadata.namespace` is
# OPTIONAL — absent = the unscoped root (not the `default` tenant). Exclusive
# with nfs/cifs/webdav, device, a non-local driver and provision.
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: team-share
namespace: teamA # PT: escopa o nome e o directório de dados; omite = raiz sem dono | EN: scopes the name and data dir; omit = unscoped root
spec:
share:
from: nfs-media # PT: obrigatório — o volume-pai de onde talhar (alias: storageRef) | EN: required — the parent volume to carve out of (alias: storageRef)
quota: 5G # PT: quota SOFT desta fatia (uso medido + alerta); omite = ilimitada | EN: SOFT quota for this slice (measured usage + alert); omit = unlimited
alertPct: 90
# ── 7. provision.truenas: — criar o dataset/quota/export numa NAS e montar ───
# provision.truenas: — create the dataset/quota/export on a NAS, then mount
# PT: OPCIONAL (ADR-0009); sem este bloco o Kind comporta-se exactamente como
# antes. Cria na NAS: o dataset, a sua quota, o dono/modo do mountpoint e um
# export NFS restrito às redes listadas. `server`/`share` do mount são
# DERIVADOS do que o aparelho reportou, nunca escritos duas vezes. Destruir
# o lado remoto (irreversível): `delonix volume rm truenas-dados --destroy-remote`.
# EN: OPTIONAL (ADR-0009); without this block the Kind behaves exactly as before.
# Makes on the NAS: the dataset, its quota, the owner/mode of the mountpoint
# and an NFS export restricted to the listed networks. The mount's
# `server`/`share` are DERIVED from what the appliance reported, never written
# twice. Destroy the remote side (irreversible):
# `delonix volume rm truenas-dados --destroy-remote`.
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: truenas-dados
spec:
provision:
truenas:
url: https://192.168.122.83 # PT: obrigatório; o esquema é teu (http:// põe a chave em claro) | EN: required; the scheme is yours (http:// puts the key on the wire in clear)
apiKeySecret: nas-creds # PT: Secret com a chave `apiKey` (ou `token`); preferido a uma conta | EN: Secret with key `apiKey` (or `token`); preferred over an account
insecureTLS: true # PT: um TrueNAS de fábrica serve um certificado auto-assinado | EN: a stock TrueNAS serves a self-signed certificate
dataset: tank/dados # PT: obrigatório: <pool>/<nome>; a pool tem de existir | EN: required: <pool>/<name>; the pool must already exist
quota: 2G # PT: quota NA NAS (mínimo 1 GiB) | EN: quota ON THE NAS (min 1 GiB)
owner:
uid: 1000 # PT: obrigatório dentro de owner | EN: required inside owner
gid: 1000 # PT: obrigatório dentro de owner | EN: required inside owner
mode: "0770" # PT: octal, entre aspas (um 0770 nu em YAML é o decimal 770) | EN: octal, quoted (a bare 0770 in YAML is decimal 770)
share: # PT: omite este bloco para aprovisionar sem exportar | EN: drop this block to provision without exporting
networks: ["192.168.122.0/24"] # PT: CIDRs autorizados a montar (tem de ser não-vazio) | EN: CIDRs allowed to mount (must be non-empty)
readOnly: false
maprootUser: root
maprootGroup: root
# ── 7b. provision.truenas: com conta em vez de chave de API ──────────────────
# provision.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 # PT: nome da conta | EN: account name
passwordSecret: nas-creds # PT: Secret com a chave `password` (ou `password:` inline) | EN: Secret with key `password` (or inline `password:`)
insecureTLS: true
dataset: tank/account
# ── 8. pool: — volume alocado numa storage pool (ADR-0067) ───────────────────
# pool: — a volume allocated in a storage pool (ADR-0067)
# PT: `pool` é o NOME de um `kind: StoragePool` (ver storagepool.yaml). Precisa
# de `size` (nesta forma o tamanho É o limite, não há `quota`); exclusivo com
# qualquer outro backing. A pool tem de estar declarada no allowlist do nó e
# aplicada antes.
# EN: `pool` is the NAME of a `kind: StoragePool` (see storagepool.yaml). Needs a
# `size` (here the size IS the limit, there is no `quota`); exclusive with
# every other backing. The pool must be in the node's allowlist and applied
# first.
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: pg-data
spec:
pool: fast # PT: o `kind: StoragePool` chamado `fast` | EN: the `kind: StoragePool` named `fast`
size: 10G # PT: tamanho pedido à pool (obrigatório com pool) | EN: size asked of the pool (required with pool)
alertPct: 85
Campos de spec — 13spec fields — 13
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
alertPct | integer | — | — | Usage percentage above which `ls`/`describe` flag a WARN (default 90). The record has carried this since volumes had quotas; the manifest could not set it, so a declarative volume was stuck with the default while the CLI could change it. It arrived with the `share:` block below, which needs it, and applies to every volume for the same reason it always did. |
cifs | NetShareSpec | — | — | SMB/CIFS share (Samba, Windows, TrueNAS SMB). |
device | string | — | — | — |
driver | string | — | "local" | — |
mountOptions | string | — | — | Canonical `mountOptions` (uniform with `kind: Storage`); `options` is still accepted (backward-compat). |
nfs | NetShareSpec | — | — | NFS export. One of the three network-share blocks — the block's NAME is the type, so a type cannot contradict its own declaration (the same shape `kind: Workload` uses for `spec.container`/`spec.vm`). |
options | object | — | — | — |
pool | string | — | — | The storage pool this volume is allocated in (ADR-0067) — the NAME of a `kind: StoragePool`. Needs `size`; exclusive with every other backing. |
provision | ProvisionSpec | — | — | OPTIONAL: create what the share block consumes, instead of requiring it to exist already (ADR-0009). Absent, this Kind behaves exactly as before. |
quota | string | — | — | — |
share | ShareBlock | — | — | A subdirectory carved out of ANOTHER volume, with its own name, quota and consumers — what `kind: ShareVolume` used to be a Kind of its own for. It is a block and not a Kind because a share IS a volume: it was already registered as one (`VolumeStore::register_external`), and the separate Kind only added a second record whose one unique field was the parent's name. The block sits beside `nfs:`/`cifs:`/`webdav:` and is exclusive with them for the reason `net_share` gives: a volume is one mount, and a share does not mount anything — it points inside a mount someone else made. |
size | string | — | — | The size asked of the pool (`10G`). Only with `pool`: a volume outside a pool has a `quota`. |
webdav | NetShareSpec | — | — | WebDAV (Nextcloud, ownCloud). |
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 vm image 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 vm image build.
# ============================================================================
# kind: Image — garantir que uma imagem está presente: PUXAR ou CONSTRUIR
# kind: Image — ensure an image is present: PULL it or BUILD it
#
# PT: `pull` e `build` são MUTUAMENTE EXCLUSIVOS (um documento cada). Puxar é
# idempotente; a identidade de uma imagem é a sua REFERÊNCIA, não o
# `metadata.name`. `pullSecret` nomeia um `kind: Secret` com as credenciais
# do registo — só faz sentido com `pull:` (um `build:` produz a imagem
# localmente e não autentica em lado nenhum, por isso um pullSecret aí é
# recusado). Uma imagem construída NÃO converge: não há cache de build, cada
# apply reconstrói e substitui a tag.
# EN: `pull` and `build` are MUTUALLY EXCLUSIVE (one document each). A pull is
# idempotent; an image's identity is its REFERENCE, not `metadata.name`.
# `pullSecret` names a `kind: Secret` with the registry credentials — only
# meaningful with `pull:` (a `build:` produces the image locally and
# authenticates nowhere, so a pullSecret there is refused). A built image does
# NOT converge: there is no build cache, every apply rebuilds and replaces the
# tag.
#
# PT: Aplicar: delonix stack apply -f examples/artifact/image.yaml
# EN: Apply: delonix stack apply -f examples/artifact/image.yaml
# ============================================================================
# ── 1. pull — puxar uma imagem pública (idempotente) ────────────────────────
# pull — pull a public image (idempotent)
---
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: alpine
spec:
pull: alpine:3.19 # PT: puxa esta referência | EN: pull this reference
# ── Secret de apoio — credenciais do registo privado abaixo ──────────────────
# Supporting Secret — registry credentials for the private pull below
# PT: Da ENVIRONMENT (nada secreto fica no manifesto; é a forma que um job de CI
# tem). Alternativa: `stringData: { username, password }` (texto no ficheiro).
# EN: From the ENVIRONMENT (nothing secret is written in the manifest; the shape
# a CI job has). Alternative: `stringData: { username, password }` (cleartext).
---
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: registry-cred
spec:
fromEnv:
username: REGISTRY_USER
password: REGISTRY_TOKEN
# ── 2. pull privado — credenciais nomeadas pelo manifesto ────────────────────
# private pull — credentials named by the manifest
# PT: Sem `pullSecret`, um pull usa o cofre de credenciais da MÁQUINA
# (`delonix image login`) — estado por-máquina que um manifesto não carrega,
# logo aplicava limpo no host onde alguém fez login e falhava em todos os
# outros. Nomear um Secret torna o documento auto-suficiente.
# EN: Without `pullSecret`, a pull uses the MACHINE's credential vault
# (`delonix image login`) — per-machine state a manifest cannot carry, so it
# applied cleanly on the host where someone logged in and failed on every
# other one. Naming a Secret makes the document self-contained.
---
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: private-app
spec:
pull: ghcr.io/angolardevops/private-app:1.0
pullSecret: registry-cred # PT: `kind: Secret` com username+password; só com pull | EN: `kind: Secret` with username+password; only with pull
# ── 3. build — construir a partir de um Delonixfile/Dockerfile ───────────────
# build — build from a Delonixfile/Dockerfile (every build field)
# PT: Todos os campos de BuildSpec. Só `tag` é obrigatório; os restantes espelham
# as flags do CLI (`delonix image build`).
# EN: Every BuildSpec field. Only `tag` is required; the rest mirror the CLI's
# flags (`delonix image build`).
---
apiVersion: artifact.delonix.io/v1alpha1
kind: Image
metadata:
name: app-built
spec:
build:
tag: app:dev # PT: obrigatório — a tag a produzir | EN: required — the tag to produce
context: . # PT: directório de contexto (default ".") | EN: build context directory (default ".")
file: Delonixfile # PT: caminho do ficheiro (default: ./Delonixfile, depois ./Dockerfile) | EN: file path (default: ./Delonixfile, then ./Dockerfile)
target: runtime # PT: multi-stage: construir até esta etapa (nome ou índice) | EN: multi-stage: build up to this stage (name or index)
platform: linux/amd64 # PT: arquitectura-alvo, como o --platform do CLI | EN: target architecture, like the CLI's --platform
noCache: true # PT: ignora a cache de camadas, como --no-cache | EN: bypass the layer cache, like --no-cache
buildArgs: # PT: overrides de ARG, KEY=VALUE (só para ARGs que o ficheiro declara) | EN: ARG overrides KEY=VALUE (only for ARGs the file declares)
- VERSION=1.2.3
- CHANNEL=stable
secrets: # PT: segredos de build, `id=<nome>,src=<path>`; usados com | EN: build-time secrets, `id=<name>,src=<path>`; used with
- id=npmrc,src=./npmrc.txt # RUN --mount=type=secret,id=npmrc — nunca fica numa camada / never lands in a layer
Campos de spec — 3spec fields — 3
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
build | BuildSpec | — | — | — |
pull | string | — | — | — |
pullSecret | string | — | — | `kind: Secret` holding `username`/`password` for the registry this image is pulled from. Without it the pull uses the machine's credential vault (`delonix image login`) — per-MACHINE state that a manifest cannot carry. A `kind: Image` naming a private registry therefore applied cleanly on the host where someone had logged in and failed on every other one, with an authentication error about a registry the manifest never mentioned a credential for. Naming a Secret makes the document self-contained, which is the whole point of GitOps. Only meaningful with `pull:` — a `build:` produces an image locally and authenticates nowhere. |
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.
# ============================================================================
# kind: VirtualMachine — uma VM declarativa (libvirt / Cloud Hypervisor / Proxmox)
# kind: VirtualMachine — a declarative VM (libvirt / Cloud Hypervisor / Proxmox)
#
# PT: `disk` (imagem base do store de VM, ou um caminho qcow2/raw) é obrigatório,
# salvo quando a imagem é CONSTRUÍDA de um VMfile (`spec.build`). Tudo o que
# é neutro ao hipervisor pode escrever-se achatado no topo de `spec` OU
# agrupado (resources/network/boot/cloudInit); o que só UM hipervisor entende
# vai em `provider: { type, spec }`. Imagem base: `delonix vm image ls`.
# EN: `disk` (a base image from the VM store, or a qcow2/raw path) is required
# unless the image is BUILT from a VMfile (`spec.build`). Everything
# hypervisor-neutral may be written flat at the top of `spec` OR grouped
# (resources/network/boot/cloudInit); what only ONE hypervisor understands
# goes under `provider: { type, spec }`. Base image: `delonix vm image ls`.
#
# PT: Aplicar: delonix stack apply -f examples/compute/virtualmachine.yaml
# EN: Apply: delonix stack apply -f examples/compute/virtualmachine.yaml
# ============================================================================
# ── 1. recursos de apoio — um Volume 9p, um Volume NAS e uma Network ─────────
# supporting resources — a 9p Volume, a NAS Volume and a Network
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: vm-data # PT: partilhado para o guest por virtio-9p (backend libvirt) | EN: shared into the guest over virtio-9p (libvirt backend)
spec:
driver: local
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: dados-nas # PT: um volume de rede (NAS) montado na VM por 9p | EN: a network (NAS) volume mounted into the VM via 9p
spec:
nfs:
server: 10.0.0.5
share: /mnt/pool/dados
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: vm-net # PT: a rede SDN a que o tap da VM se liga (backend CH) | EN: the SDN network the VM's tap joins (CH backend)
spec:
driver: bridge
# ── 2. forma agrupada com os defaults à vista ───────────────────────────────
# grouped shape with the defaults made explicit
---
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: node1
spec:
disk: k8s-golden # PT: obrigatório — qcow2/raw base (faz-se um overlay por VM) | EN: required — base qcow2/raw (an overlay is made per VM)
restartPolicy: null # PT: no | on-failure | always | EN: no | on-failure | always
resources:
vcpus: 2
memory: 2G # PT: "2G" | "1024M" (sufixo "…i" k8s aceite) | EN: "2G" | "1024M" (a "…i" suffix is accepted, k8s-style)
hugepages: false
cpuAffinity: null # PT: fixar vCPUs, ex. "8-15" | EN: pin vCPUs, e.g. "8-15"
network:
name: vm-net # PT: rede de ingresso do tap da VM | EN: ingress network for the VM's tap
mode: null # PT: só libvirt: user | nat | bridge | EN: libvirt only: user | nat | bridge
bridge: null # PT: bridge do host / rede libvirt | EN: host bridge / libvirt network
staticIp: null # PT: modo nat libvirt: reserva DHCP na rede libvirt | EN: libvirt nat mode: DHCP reservation on the libvirt network
# PT: Mecanismo de arranque: firmware (imagens cloud, default) OU kernel directo — escolhe um.
# EN: Boot mechanism: firmware (cloud images, the default) OR a direct kernel — pick one.
boot:
firmware: null # PT: caminho de firmware UEFI (típico em imagens cloud) | EN: UEFI firmware path (typical for cloud images)
kernel: null # PT: arranque por kernel directo (alternativa a firmware) | EN: direct kernel boot (alternative to firmware)
initrd: null # PT: initramfs para o kernel directo | EN: initramfs for direct kernel boot
cmdline: null # PT: cmdline do kernel para o arranque directo | EN: kernel cmdline for direct boot
# PT: cloud-init — paridade com `vm create`; gera-se um seed ISO NoCloud a menos que dês um pronto em `seed`.
# EN: cloud-init — full parity with `vm create`; a NoCloud seed ISO is auto-generated unless you hand it one via `seed`.
cloudInit:
seed: null # PT: caminho de um ISO NoCloud pronto (anula os campos abaixo) | EN: path to a prebuilt NoCloud ISO (overrides the fields below)
hostname: null # PT: hostname aplicado no 1º arranque | EN: hostname applied on first boot
sshKeys: [] # PT: chaves públicas: "ssh-ed25519 AAAA…" ou "@/caminho" | EN: authorized public keys: "ssh-ed25519 AAAA…" or "@/path"
userData: null # PT: user-data cloud-init teu (path/@path) — substitui o gerado | EN: your own cloud-init user-data (path/@path) — replaces the generated one
# PT: O hipervisor e o que só ele entende. `type` escolhe o provider (omitir o bloco = default); o key que o type não tem é recusado.
# EN: The hypervisor and what only it understands. `type` picks the provider (omit the block = default); a key the type lacks is refused.
provider:
type: libvirt # PT: cloud-hypervisor | libvirt | proxmox | EN: cloud-hypervisor | libvirt | proxmox
spec: # PT: libvirt: paridade XML, tudo opcional | EN: libvirt: full XML parity, all optional
machine: null # PT: tipo de máquina (default q35) | EN: machine type (default q35)
cpuModel: null # PT: host-passthrough (default) | host-model | modelo nomeado | EN: host-passthrough (default) | host-model | a named model
cpuTopology: null # PT: { sockets: 1, cores: 4, threads: 2 } | EN: { sockets: 1, cores: 4, threads: 2 }
tpm: false # PT: TPM 2.0 emulado | EN: add an emulated TPM 2.0
video: null # PT: virtio | qxl | vga | none | EN: virtio | qxl | vga | none
bootOrder: [] # PT: ex. [cdrom, hd] | EN: e.g. [cdrom, hd]
extraDisks: [] # PT: [{ source, bus, format, readOnly }] | EN: [{ source, bus, format, readOnly }]
extraNics: [] # PT: [{ type, source, model }] | EN: [{ type, source, model }]
xmlOverlay: [] # PT: fragmentos <device> XML crus (manifestos de confiança) | EN: raw <device> XML fragments (trusted manifests only)
xml: null # PT: <domain> XML completo, usado tal e qual | EN: full <domain> XML, used verbatim
devices: [] # PT: passthrough VFIO PCI (caminhos sysfs) — CH + libvirt | EN: VFIO PCI passthrough (sysfs paths) — CH + libvirt
volumes: [] # PT: montagens 9p de um Volume: [{ name, mountPath, readOnly }] | EN: 9p mounts from a Volume: [{ name, mountPath, readOnly }]
vnc: false # PT: consola gráfica (só backend libvirt) | EN: graphical console (libvirt backend only)
# ── 3. forma plana completa — todos os campos do backend libvirt ─────────────
# full flat — every field the libvirt backend understands
---
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: full-flat
namespace: default # PT: namespace de isolamento (imposto só no backend CH) | EN: isolation namespace (enforced on the CH backend only)
spec:
disk: delonix-vm-base:ubuntu-24.04 # PT: imagem base: nome no store de VM, ou caminho (overlay por VM) | EN: base image: a name in the VM store, or a path (overlay per VM)
provider: { type: libvirt } # PT: cloud-hypervisor | libvirt | proxmox (omitir = auto) | EN: cloud-hypervisor | libvirt | proxmox (omit = auto)
restartPolicy: always # PT: no | on-failure | always | EN: no | on-failure | always
vcpus: 4 # PT: omitido => default da imagem, senão 1 | EN: omitted => the image's default, else 1
memory: 4G # PT: "512M" | "2G" | "2Gi" | EN: "512M" | "2G" | "2Gi"
hugepages: false # PT: apoiar a RAM do guest com hugepages | EN: back guest RAM with hugepages
cpuAffinity: "0-3" # PT: fixar vCPUs a CPUs do host | EN: pin vCPUs to host CPUs
network: vm-net # PT: nome de rede SDN (`ingress` é o default embutido) | EN: SDN network name (`ingress` is the built-in default)
netMode: nat # PT: só libvirt: user | nat | bridge (`nat` dá IP observável) | EN: libvirt only: user | nat | bridge (`nat` gives an observable IP)
ip: 192.168.122.50 # PT: IP estático: reserva DHCP na rede libvirt (só modo nat) | EN: static IP: DHCP reservation on the libvirt network (nat mode only)
vnc: true # PT: consola gráfica (só backend libvirt) | EN: graphical console (libvirt backend only)
devices: # PT: passthrough VFIO PCI, caminho sysfs | EN: VFIO PCI passthrough, sysfs path
- /sys/bus/pci/devices/0000:01:00.0
# PT: cloud-init: gera-se um seed ISO NoCloud destes campos salvo se der `seed`.
# EN: cloud-init: a NoCloud seed ISO is generated from these unless `seed` is given.
hostname: full-flat # PT: hostname aplicado no 1º arranque | EN: hostname applied on first boot
sshKeys: # PT: chaves públicas: "ssh-ed25519 AAAA…" ou "@/caminho.pub" | EN: public keys: "ssh-ed25519 AAAA…" or "@/path.pub"
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleExampleExampleExampleExampleExample admin@example"
userData: "@/etc/delonix/examples/user-data.yaml" # PT: user-data teu (path/@path); substitui o gerado | EN: your own user-data (path/@path); replaces the generated one
# PT: volumes partilhados para o guest por virtio-9p (`kind: Volume`, por nome)
# EN: volumes shared into the guest over virtio-9p (`kind: Volume`, by name)
volumes:
- name: vm-data
mountPath: /mnt/data # PT: onde o guest o monta | EN: where the guest mounts it
readOnly: false
# PT: knobs avançados do libvirt / EN: advanced libvirt knobs
machine: q35 # PT: tipo de máquina (default q35) | EN: machine type (default q35)
cpuModel: host-passthrough # PT: host-passthrough (default) | host-model | modelo | EN: host-passthrough (default) | host-model | a model
cpuTopology: { sockets: 1, cores: 2, threads: 2 }
tpm: true # PT: TPM 2.0 emulado | EN: emulated TPM 2.0
video: virtio # PT: virtio | qxl | vga | none | EN: virtio | qxl | vga | none
bootOrder: [hd, cdrom] # PT: ordem de dispositivos de arranque | EN: OS boot device order
extraDisks: # PT: discos extra além do overlay + seed | EN: extra disks beyond the main overlay + seed
- source: /var/lib/libvirt/images/extra.qcow2
device: disk # PT: disk (default) | cdrom | EN: disk (default) | cdrom
bus: virtio # PT: virtio (default) | sata | scsi | ide | EN: virtio (default) | sata | scsi | ide
format: qcow2 # PT: qcow2 (default) | raw | EN: qcow2 (default) | raw
readOnly: false
target: vdb # PT: dev de destino explícito (auto se omitido) | EN: explicit target dev (auto when omitted)
extraNics: # PT: interfaces de rede extra além da primária | EN: extra NICs beyond the primary one
- type: network # PT: network (rede libvirt) | bridge (host) | user | EN: network (libvirt network) | bridge (host) | user
source: default
model: virtio # PT: virtio (default) | e1000 | … | EN: virtio (default) | e1000 | …
mac: "52:54:00:12:34:56" # PT: MAC fixo (aleatório se omitido) | EN: fixed MAC (random when omitted)
libvirtXmlOverlay: # PT: fragmentos <device> crus injectados antes de </devices> (SÓ confiança) | EN: raw <device> fragments injected before </devices> (TRUSTED only)
- "<rng model='virtio'><backend model='random'>/dev/urandom</backend></rng>"
# ── 4. forma agrupada completa — os mesmos knobs, por tema ───────────────────
# full grouped — 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: [] # PT: passthrough VFIO (fica no topo) | EN: VFIO passthrough (stays top-level)
volumes: # PT: montagens 9p (fica no topo) | EN: 9p mounts (stays top-level)
- { name: vm-data, mountPath: /mnt/data, readOnly: true }
vnc: true # PT: fica no topo | EN: stays top-level
resources:
vcpus: 2
memory: 2G
hugepages: false
cpuAffinity: "0-1"
network: # PT: um mapa aqui (uma string é a forma plana) | EN: a mapping here (a plain string is the flat form)
name: vm-net
mode: nat # PT: => netMode | EN: => netMode
staticIp: 192.168.122.51 # PT: => ip (modo nat) | EN: => ip (nat mode)
# bridge: br0 # PT: => bridge (host bridge, modo bridge) — ver `bridge-vm` | EN: => 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 # PT: seed pronto — ver `prebuilt-seed` | EN: prebuilt seed — see `prebuilt-seed`
provider: # PT: o hipervisor e o que só ele entende | EN: the hypervisor and what only it understands
type: libvirt
spec:
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:
- "<memballoon model='virtio'/>"
# xml: "<domain …>" # PT: <domain> inteiro, tal e qual — ver `raw-domain` | EN: whole <domain>, verbatim — see `raw-domain`
# ── 5. modo bridge — a VM liga-se a uma bridge do host (incompatível com ip estático, que é só-nat)
# 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
provider: { type: libvirt }
netMode: bridge
bridge: br0 # PT: bridge do host / rede libvirt a que ligar | EN: host bridge / libvirt network to attach to
vcpus: 2
memory: 2G
# ── 6. arranque por kernel directo (alternativa ao firmware) — Cloud Hypervisor
# direct-kernel boot (alternative to firmware) — Cloud Hypervisor
---
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: direct-kernel
spec:
disk: delonix-vm-base:fedora-42
provider: { type: cloud-hypervisor }
network: vm-net
kernel: /var/lib/delonix/kernels/vmlinuz-6.14.0 # PT: arranque por kernel directo | EN: direct kernel boot
initrd: /var/lib/delonix/kernels/initramfs-6.14.0.img
cmdline: "console=ttyS0 root=/dev/vda1 rw" # PT: linha de comando do kernel | EN: kernel command line
vcpus: 2
memory: 2G
# ── 7. firmware UEFI explícito (modo habitual de uma imagem cloud) — Cloud Hypervisor
# 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
provider: { type: cloud-hypervisor }
network: vm-net
firmware: /usr/local/share/delonix/CLOUDHV.fd # PT: caminho de firmware UEFI | EN: UEFI firmware path
vcpus: 2
memory: 2G
# ── 8. seed cloud-init pronto — substitui o gerado (hostname/sshKeys/userData não se põem)
# prebuilt cloud-init seed — replaces the generated one (hostname/sshKeys/userData 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 # PT: ISO NoCloud entregue ao guest tal e qual | EN: NoCloud ISO handed to the guest as-is
# ── 9. escape hatch — o <domain> XML libvirt inteiro, usado tal e qual (SÓ confiança)
# escape hatch — the whole libvirt <domain> XML, used verbatim (TRUSTED only)
---
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: raw-domain
spec:
disk: delonix-vm-base:ubuntu-24.04
provider:
type: libvirt
spec:
xml: |
<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>
# ── 10. o disco é CONSTRUÍDO de um VMfile (spec.build) — incompatível com `disk`
# 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: . # PT: contexto do build, relativo à pasta DESTE manifesto (default ".") | EN: build context, relative to THIS manifest's folder (default ".")
file: VMfile # PT: o VMfile (default <context>/VMfile) | EN: the VMfile (default <context>/VMfile)
tag: built-disk:latest # PT: tag da imagem produzida, depois usada como disco (default <name>:latest) | EN: tag of the produced image, used as the VM's disk (default <name>:latest)
compress: true # PT: comprimir o resultado em zstd (default true) | EN: zstd-compress the result (default true)
network: false # PT: dar acesso de rede ao build (default false: reprodutível) | EN: give the build network access (default false: reproducible)
vcpus: 2
memory: 2G
# ── 11. volumes 9p de um NAS — storage a uma VM sem escrever cloud-init nem XML
# 9p volumes from a NAS — storage for a VM without writing cloud-init or XML
---
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata:
name: app-vm
spec:
disk: k8s-golden # PT: imagem base (qcow2/raw) | EN: base image (qcow2/raw)
vcpus: 2
memory: 2G
# PT: só o backend libvirt materializa 9p; o apply escolhe-o quando há volumes (CH não suporta 9p).
# EN: only the libvirt backend materializes 9p; apply selects it when there are volumes (CH has no 9p).
volumes:
- name: dados-nas # PT: o kind: Volume NAS declarado no topo | EN: the NAS kind: Volume declared at the top
mountPath: /mnt/dados # PT: onde a VM o monta | EN: where the VM mounts it
readOnly: false
Campos de spec — 60spec fields — 60
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
allowMacSpoofing | boolean | — | false | Opt THIS VM out of the libvirt anti-spoofing filter on its primary NIC (libvirt, `netMode: nat|bridge` only). The guest may then send frames with any source MAC — what a hypervisor-in-a-VM needs for its own guests on a bridge, and what a hostile guest needs to impersonate a neighbour. Off by default; refused where there is no filter (ADR-0055). |
allow_mac_spoofing | object | — | — | — |
backend | string | — | — | — |
boot | object | — | — | — |
bootOrder | array<string> | — | [] | OS boot device order, e.g. `[cdrom, hd]`. |
boot_order | object | — | — | — |
bridge | string | — | — | — |
build | VmBuildSpec | — | — | Build the base disk from a `VMfile`, instead of naming one that already exists — the same shape `kind: Image` has (`pull:` or `build:`), applied to VMs. Without it, a project whose VM image is built from a `VMfile` needed two commands and a hand-copied tag between them: `delonix vm image build -t x` and then a manifest saying `disk: x`. The tag was written in two places and nothing kept them in step. |
cloudInit | object | — | — | — |
cmdline | string | — | — | — |
cpuAffinity | string | — | — | Canonical `cpuAffinity`; `cpu_affinity` stays accepted (back-compat). |
cpuModel | string | — | — | CPU mode/model: `host-passthrough` (default), `host-model`, or a named model. |
cpuTopology | CpuTopologySpec | — | — | CPU topology (sockets/cores/threads). |
cpu_affinity | object | — | — | — |
cpu_model | object | — | — | — |
cpu_topology | object | — | — | — |
credentials | CredentialsSpec | — | — | The guest's login (ADR-0048 D4): a user and a REFERENCE to a `kind: Secret` key, never the value. Shown by `vm ls --credentials`. |
devices | array<string> | — | [] | — |
disk | string | — | "" | The base disk: a VM image name in the store, or a path. Optional ONLY because [`build`](Self::build) can produce it. Exactly one of the two — a `kind: VirtualMachine` with neither has no disk to boot, and one with both is two answers to the same question (see [`VmSpec::resolve_disk`]). |
expose | array<VmExposeSpec> | — | — | HTTP/S services listening inside the guest, published by name (ADR-0046). Lowered at load into a synthetic `kind: HTTPRoute` named `<vm>-expose`; the key never reaches the VM apply. See [`super::vm_expose`]. |
extraDisks | array<ExtraDiskSpec> | — | [] | Extra disks beyond the main overlay + seed. |
extraNics | array<ExtraNicSpec> | — | [] | Extra network interfaces beyond the primary one. |
extra_disks | object | — | — | — |
extra_nics | object | — | — | — |
firmware | string | — | — | — |
hostname | string | — | — | cloud-init: hostname applied on first boot (CLI `--hostname`). Without an explicit `seed`, a NoCloud ISO is generated from these fields — full parity with `vm create` in the declarative path. |
hugepages | boolean | — | false | — |
initrd | string | — | — | — |
ip | string | — | — | Static IP (libvirt `nat` mode): DHCP reservation on the libvirt network. |
kernel | string | — | — | — |
libvirt | object | — | — | — |
libvirtXml | string | — | — | Full `<domain>` XML used verbatim (ultimate escape hatch; trusted only). |
libvirtXmlOverlay | array<string> | — | [] | Raw libvirt XML fragments injected before `</devices>` (trusted manifests). |
libvirt_xml | object | — | — | — |
libvirt_xml_overlay | object | — | — | — |
machine | string | — | — | Machine type (default `q35`). |
memory | string | — | — | RAM (`512M`, `2G`, …). Optional for the same reason as [`vcpus`](Self::vcpus); the fallback when neither the manifest nor the image says anything is `1G`. |
memoryMax | string | — | — | The memory hotplug ceiling, same rule as `vcpusMax` (`4G`, `4096M`, `4Gi`). Never below `memory`. |
memory_max | object | — | — | — |
netMode | string | — | — | Canonical `netMode`; `net_mode` stays accepted (back-compat). |
net_mode | object | — | — | — |
network | string | object | — | — | — |
provider | object | — | — | — |
requiredCapabilities | array<string> | — | [] | Capabilities the backend must support on this host, by catalog name (`vm.snapshot.memory`, `vm.namespace-isolation`, … — `delonix provider ls` lists them; CLI `--require`). An unknown name is refused as invalid; a backend that lacks one is refused before anything is created, and auto-detection only picks a backend that has them all (ADR-0050 D6). |
required_capabilities | object | — | — | — |
resources | object | — | — | — |
restartPolicy | string | — | — | Canonical `restartPolicy` (uniform with `Container`); `restart_policy` stays accepted so earlier manifests don't break. |
restart_policy | object | — | — | — |
seed | string | — | — | — |
sshKeys | array<string> | — | [] | cloud-init: authorized public SSH keys (CLI `--ssh-key`, repeatable). Each is `ssh-ed25519 AAAA…` or `@/path` to read from a file. |
ssh_keys | object | — | — | — |
tpm | boolean | — | false | Emulated TPM 2.0. |
userData | string | — | — | cloud-init: your own `user-data` (replaces the generated one) — a path or `@/path` (CLI `--user-data`). Full control for whoever needs it. |
user_data | object | — | — | — |
vcpus | integer | — | — | vCPUs. **Optional so that "omitted" and "1" are different things**: an image built from a `VMfile` records its own `VCPUS`/`MEMORY`, and those only apply where the manifest said nothing. `default_value_t` had to go from the CLI flags for exactly this reason — the declarative path is the same problem. Nothing is declared ⇒ [`resolve_vm_defaults`] falls back to 1. |
vcpusMax | integer | — | — | The vCPU hotplug CEILING, fixed at boot (ADR-0068 D3): the most this VM can be grown to LIVE. Never below `vcpus`. Omitted = no headroom, so any growth converges across a reboot (`stack apply --reboot`). |
vcpus_max | object | — | — | — |
video | string | — | — | Video model (`virtio`|`qxl`|`vga`|`none`). |
vnc | boolean | — | false | — |
volumes | array<VmVolumeSpec> | — | [] | Volumes/Storage to mount inside the VM (virtio-9p) — closes the gap of giving storage to a VM without writing cloud-init/XML. See `VmVolumeSpec`. |
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.
# ============================================================================
# kind: Container — o workload do dia-a-dia (formas plana e agrupada)
# kind: Container — the everyday workload (flat and grouped shapes)
#
# PT: `image` é o ÚNICO campo obrigatório; tudo o resto tem default. Os campos
# podem escrever-se achatados no topo de `spec` (forma histórica) OU
# agrupados por tema (resources/network/security/storage/env/limits) — as
# duas formas descrevem o mesmo contentor e são aceites. ATENÇÃO: o default
# de `restartPolicy` é "no" (não reinicia).
# EN: `image` is the ONLY required field; everything else has a default. Fields
# may be written flat at the top of `spec` (historic form) OR grouped by
# concern (resources/network/security/storage/env/limits) — both shapes
# describe the same container and are accepted. NOTE: the default of
# `restartPolicy` is "no" (never restarts).
#
# PT: A forma k8s (`spec.containers[]`) num `kind: Container` é obsoleta — para
# isso usa `kind: Pod` (ver compute/pod.yaml).
# EN: The k8s shape (`spec.containers[]`) on a `kind: Container` is a sunset form
# — use `kind: Pod` for that (see compute/pod.yaml).
#
# PT: Aplicar: delonix stack apply -f examples/compute/container.yaml
# EN: Apply: delonix stack apply -f examples/compute/container.yaml
# ============================================================================
# ── 1. mínimo — só o campo obrigatório ──────────────────────────────────────
# minimal — the only mandatory field
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: minimal
spec:
image: alpine:3.20 # PT: único campo obrigatório | EN: the only required field
# ── 2. forma agrupada com os defaults à vista ───────────────────────────────
# grouped shape with the defaults made explicit
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web
spec:
image: nginx:alpine # PT: obrigatório | EN: required
command: [] # PT: substitui o CMD da imagem, ex. ["nginx","-g","daemon off;"] | EN: overrides the image CMD
entrypoint: null # PT: substitui o ENTRYPOINT ("" limpa-o) | EN: overrides the ENTRYPOINT ("" clears it)
user: null # PT: UID/GID ou nome, ex. "1000:1000" | EN: UID/GID or name, e.g. "1000:1000"
hostname: null # PT: hostname do contentor (default: o nome) | EN: container hostname (default: the name)
detach: true # PT: corre em segundo plano (um manifesto é declarativo) | EN: run in the background
restartPolicy: "no" # PT: no (default) | on-failure[:max] | always | unless-stopped | EN: no (default) | on-failure[:max] | always | unless-stopped
addHost: [] # PT: entradas extra em /etc/hosts, `nome:ip` | EN: extra /etc/hosts entries, `name:ip`
resources: # PT: limites cgroup v2 | EN: cgroup v2 limits
memory: max # PT: 64M | 2G | max (sem tecto) | EN: 64M | 2G | max (no cap)
cpus: "1.0" # PT: núcleos de CPU | EN: CPU cores
cpuWeight: null # PT: peso relativo de CPU sob contenção (1-10000) | EN: relative CPU weight under contention (1-10000)
cpuset: null # PT: fixar a CPUs, ex. "0-3" | EN: pin to CPUs, e.g. "0-3"
ioWeight: null # PT: peso relativo de I/O | EN: relative I/O weight
network:
name: host # PT: host | none | <rede criada por `network create`> | EN: host | none | <a network from `network create`>
ports: [] # PT: ["8080:80"] — dá ao contentor netns próprio + NAT slirp | EN: ["8080:80"] — gives its own netns + slirp NAT
expose: null # PT: porta HTTP a auto-registar no proxy L7 (ver kind: Gateway) | EN: HTTP port to auto-register in the L7 proxy
alias: [] # PT: aliases DNS na rede | EN: DNS aliases on the network
knows: [] # PT: restringe a resolução de nomes a estes contentores | EN: restrict name resolution to these containers
rateBps: null # PT: limite de débito de saída (ex. "10mbit"), só com rede própria | EN: egress rate limit, custom network only
rateBurst: null # PT: rajada para rateBps | EN: burst for rateBps
security:
privileged: false # PT: todas as caps, seccomp off — só workloads de confiança | EN: all caps, seccomp off — trusted only
readOnly: false # PT: rootfs só-leitura (escritas vão p/ tmpfs/volumes) | EN: read-only rootfs
capAdd: [] # PT: ["NET_ADMIN"] | EN: ["NET_ADMIN"]
capDrop: [] # PT: ["MKNOD"] | EN: ["MKNOD"]
securityOpt: [] # PT: ["seccomp=unconfined","apparmor=<perfil>"] | EN: ["seccomp=unconfined","apparmor=<profile>"]
apparmor: null # PT: perfil AppArmor | EN: AppArmor profile
selinux: null # PT: contexto SELinux | EN: SELinux context
userns: false # PT: força o user namespace subuid (default-on em rootless) | EN: force the subuid userns (default-on in rootless)
hostPid: false # PT: partilha o PID namespace do host | EN: share the host PID namespace
hostIpc: false # PT: partilha o IPC namespace do host | EN: share the host IPC namespace
detect: false # PT: seccomp em modo-log — descobre os syscalls do workload | EN: seccomp in log mode — discover syscalls
storage:
volumes: [] # PT: ["data:/var/lib","/host/path:/dentro:ro"] | EN: ["data:/var/lib","/host/path:/inside:ro"]
tmpfs: [] # PT: ["/scratch"] | EN: ["/scratch"]
env:
vars: [] # PT: ["KEY=value"] | EN: ["KEY=value"]
files: [] # PT: ["./.env"] | EN: ["./.env"]
secrets: [] # PT: nomes de secret do cofre (ver `delonix secret`); injectados como env | EN: vault secret names, injected as env
secretFiles: false # PT: true → secrets em /run/secrets/<nome> em vez de env | EN: true → secrets to /run/secrets/<name> instead of env
limits:
devices: [] # PT: ["/dev/fuse"] | EN: ["/dev/fuse"]
gpus: null # PT: all | nvidia | dri | EN: all | nvidia | dri
ulimit: [] # PT: ["nofile=1024:2048"] | EN: ["nofile=1024:2048"]
sysctl: [] # PT: ["net.core.somaxconn=1024"] | EN: ["net.core.somaxconn=1024"]
labels: [] # PT: ["tier=frontend"] | EN: ["tier=frontend"]
logDriver: null # PT: json | cri | EN: json | cri
# ── 3. recursos de apoio para os cenários completos abaixo ───────────────────
# supporting resources for the full scenarios below
---
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 # PT: injectado como env (ou /run/secrets/, ver secretFiles) | EN: injected as env (or /run/secrets/, see secretFiles)
---
apiVersion: storage.delonix.io/v1alpha1
kind: Volume
metadata:
name: app-data
spec:
driver: local
---
# PT: um contentor-par, para `knows:` ter algo a resolver.
# EN: 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
# ── 4. forma plana completa — todos os campos no topo de spec ────────────────
# full flat — every field at the top level of spec
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: full-flat
namespace: default # PT: namespace de isolamento | EN: isolation namespace
labels: # PT: labels de metadata (o motor junta as suas delonix.io/*) | EN: metadata labels (engine adds its own delonix.io/*)
tier: frontend
annotations:
example.delonix.io/purpose: full-spec-demo
spec:
image: nginx:alpine # PT: OBRIGATÓRIO (puxado se faltar) | EN: REQUIRED (pulled if missing)
detach: true # PT: corre em segundo plano | EN: run in the background
command: ["nginx", "-g", "daemon off;"] # PT: substitui o CMD da imagem | EN: overrides the image CMD
entrypoint: /docker-entrypoint.sh # PT: substitui o ENTRYPOINT ("" limpa-o) | EN: overrides the ENTRYPOINT ("" clears it)
user: "101:101" # PT: uid[:gid] ou nome a correr | EN: uid[:gid] or user name to run as
hostname: full-flat-host # PT: hostname (default: o nome) | EN: hostname (default: the name)
restartPolicy: on-failure:3 # PT: no | on-failure[:max] | always | unless-stopped (alias legado `restart`) | EN: no | on-failure[:max] | always | unless-stopped (legacy alias `restart`)
addHost: # PT: entradas extra em /etc/hosts, `nome:ip` | EN: extra /etc/hosts entries, `name:ip`
- legacy-api:10.0.0.9
labels: # PT: labels do contentor `chave=valor` (nível spec, distinto de metadata.labels) | EN: container labels `key=value` (spec-level, distinct from metadata.labels)
- tier=frontend
- team=platform
# --- rede / network ---
network: app-net # PT: host | none | <rede própria> (própria => netns na SDN) | EN: host | none | <custom network> (custom => own netns on the SDN)
ports: # PT: publica host:contentor[/proto]; [hostIp:] e gamas também | EN: publish host:container[/proto]; [hostIp:] and ranges too
- "127.0.0.1:18080:80"
- "18443:443/tcp"
- "19000-19002:9000-9002"
expose: 80 # PT: auto-registar esta porta HTTP no proxy L7 (precisa de rede própria) | EN: auto-register this HTTP port in the L7 proxy (needs a custom network)
networkAlias: # PT: nomes DNS extra na rede própria | EN: extra DNS names on the custom network
- web
- frontend
knows: # PT: restringe o que este contentor alcança a estes pares (Dependency dirigida) | EN: restrict reach to these peers (directed Dependency)
- peer
netBps: 10mbit # PT: limite de débito de saída (precisa de rede própria) | EN: egress rate limit (needs a custom network)
netBurst: 20mbit # PT: rajada para netBps | EN: burst for netBps
# --- recursos (cgroup v2) / resources (cgroup v2) ---
memory: 256M # PT: 64M | 2G | max | EN: 64M | 2G | max
cpus: "0.5" # PT: núcleos de CPU | EN: CPU cores
cpuWeight: "200" # PT: peso relativo de CPU sob contenção (1-10000) | EN: relative CPU weight under contention (1-10000)
cpuset: "0" # PT: fixar a CPUs (precisa do controlador cpuset delegado) | EN: pin to CPUs (needs the cpuset controller delegated)
ioWeight: "100" # PT: peso relativo de I/O de bloco | EN: relative block-I/O weight
cgroupParent: # PT: cgroup intermédio partilhado que limita um GRUPO de contentores | EN: shared intermediate cgroup bounding a GROUP of containers
name: app-tier # PT: um segmento de caminho seguro | EN: a single safe path segment
memoryMax: "1073741824" # PT: tecto agregado de memória (bytes) | EN: aggregate memory ceiling (bytes)
cpus: "2" # PT: núcleos de CPU agregados | EN: aggregate CPU cores
pidsMax: "512" # PT: tecto agregado de processos | EN: aggregate process ceiling
# --- segurança / security ---
readOnly: true # PT: rootfs só-leitura | EN: read-only rootfs
capDrop: ["ALL"] # PT: capabilities a largar | EN: capabilities to drop
capAdd: ["NET_BIND_SERVICE"] # PT: capabilities a repor | EN: capabilities to add back
securityOpt: # PT: opções de segurança estilo docker | EN: docker-style security options
- no-new-privileges
userns: true # PT: força o user namespace subuid (já default em rootless) | EN: force the subuid userns (already default in rootless)
# --- armazenamento / storage ---
volumes: # PT: volume nomeado ou bind mount; :ro opcional | EN: named volume or bind mount; optional :ro
- app-data:/var/lib/app
- /etc/hostname:/etc/host-name:ro
tmpfs: # PT: montagens em memória | EN: in-memory mounts
- /tmp
- /run/cache
# --- ambiente / environment ---
env: # PT: lista KEY=value | EN: KEY=value list
- TZ=Africa/Luanda
- LOG_LEVEL=info
secret: # PT: nomes de secret do cofre, injectados como env (ou ficheiros, abaixo) | EN: vault secret names, injected as env (or files, below)
- app-secret
secretFiles: false # PT: true => secrets em /run/secrets/<chave> em vez de env | EN: true => secrets as /run/secrets/<key> instead of env
# --- outros limites / misc limits ---
ulimit:
- nofile=1024:2048
sysctl:
- net.core.somaxconn=1024
logDriver: json # PT: json | cri | EN: json | cri
# ── 5. forma agrupada completa — os mesmos campos, por tema ──────────────────
# full grouped — 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: # PT: limites cgroup v2 | EN: 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: # PT: um mapa aqui (uma string é a forma plana) | EN: a mapping here (a plain string is the flat form)
name: app-net # PT: host | none | <rede própria> | EN: host | none | <custom network>
ports: ["18081:80"]
expose: 80 # PT: auto-registar no proxy L7 | EN: auto-register in the L7 proxy
alias: [web-grouped] # PT: => networkAlias | EN: => networkAlias
knows: [peer]
rateBps: 10mbit # PT: => netBps | EN: => netBps
rateBurst: 20mbit # PT: => netBurst | EN: => netBurst
security:
privileged: false # PT: true => todas as caps, seccomp off (só confiança) | EN: true => all caps, seccomp off (trusted only)
readOnly: true
capAdd: ["NET_BIND_SERVICE"]
capDrop: ["ALL"]
securityOpt: ["no-new-privileges"]
userns: true
hostPid: false # PT: partilha o pid namespace do HOST | EN: share the HOST pid namespace
hostIpc: false # PT: partilha o ipc namespace do HOST | EN: share the HOST ipc namespace
detect: false # PT: seccomp em modo-log | EN: seccomp in log mode
storage:
volumes: ["app-data:/var/lib/app"]
tmpfs: ["/tmp"]
env: # PT: env agrupado (vars/files/secrets/secretFiles) | EN: grouped env (vars/files/secrets/secretFiles)
vars: ["TZ=Africa/Luanda"]
files: [] # PT: ["./.env"] — ficheiros env lidos no apply | EN: ["./.env"] — env files read at apply time
secrets: [app-secret]
secretFiles: true # PT: secrets em /run/secrets/<chave> | EN: secrets as /run/secrets/<key>
limits:
ulimit: ["nofile=1024:2048"]
sysctl: ["net.core.somaxconn=1024"]
devices: ["/dev/null:/dev/fuse-demo"] # PT: nós de dispositivo extra | EN: extra device nodes
# ── 6. env como mapa KEY: value (o que escrevem utilizadores de compose/k8s) ──
# 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 # PT: não-strings são convertidas a string | EN: non-strings are stringified
# ── 7. primeiro plano — detach false bloqueia o apply até o processo sair ─────
# foreground — detach false blocks apply until the process exits
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: foreground
spec:
image: busybox:latest
command: ["echo", "hello"]
detach: false
restartPolicy: "no"
Campos de spec — 50spec fields — 50
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
addHost | array<string> | — | [] | — |
apparmor | string | — | — | — |
capAdd | array<string> | — | [] | — |
capDrop | array<string> | — | [] | — |
cgroupParent | SpecCgroupParent | — | — | Intermediate cgroup shared by a GROUP of containers, with its own aggregate ceiling — the only way to bound what N containers hold TOGETHER (see `delonix_compute::CgroupParent`). |
command | array<string> | — | [] | — |
cpuWeight | string | — | — | — |
cpus | string | — | — | — |
cpuset | string | — | — | — |
credentials | CredentialsSpec | — | — | The service's login (ADR-0048 D4): a user and a REFERENCE to a `kind: Secret` key, never the value. Shown by `container ls --credentials`; the value only under `--reveal`. |
detach | boolean | — | true | — |
detect | boolean | — | false | — |
devices | array<string> | — | [] | — |
entrypoint | string | — | — | — |
env | array<string> | object | — | — | — |
envFile | array<string> | — | [] | — |
expose | integer | — | — | HTTP port to auto-register in the L7 proxy (internal FQDN). See `--expose`. |
gpus | string | — | — | — |
hostIpc | boolean | — | false | — |
hostPid | boolean | — | false | — |
hostname | string | — | — | — |
image | string | simyes | — | — |
ioWeight | string | — | — | — |
knows | array<string> | — | [] | — |
labels | array<string> | — | [] | — |
limits | object | — | — | — |
logDriver | string | — | — | — |
memory | string | — | — | — |
netBps | string | — | — | — |
netBurst | string | — | — | — |
network | string | object | — | — | — |
networkAlias | array<string> | — | [] | — |
ports | array<string> | — | [] | — |
privileged | boolean | — | false | — |
readOnly | boolean | — | false | — |
resources | object | — | — | — |
restart | object | — | — | — |
restartPolicy | string | — | "no" | `no` (default) | `on-failure[:max]` | `always` | `unless-stopped` — a detached supervisor becomes the container's parent and restarts it (see `run_supervised`). This is what makes a manifest resilient. Canonical field name is `restartPolicy` (uniform with `kind: VirtualMachine`); the legacy `restart` stays accepted so existing manifests don't break. |
secret | array<string> | — | [] | — |
secretFiles | boolean | — | false | — |
security | object | — | — | — |
securityOpt | array<string> | — | [] | — |
selinux | string | — | — | — |
storage | object | — | — | — |
sysctl | array<string> | — | [] | — |
tmpfs | array<string> | — | [] | — |
ulimit | array<string> | — | [] | — |
user | string | — | — | — |
userns | boolean | — | false | — |
volumes | array<string> | — | [] | — |
NetworkPolicy (as duas direcções)
Firewall L4 declarativo por direcção (estilo k8s NetworkPolicy; o nome antigo FirewallPolicy continua aceite como alias). 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; the old name FirewallPolicy is still accepted as an alias). Each document is the desired state of one direction for one target container — allowlist + default-deny, idempotent. The kind: Egress no longer exists: it is direction: egress in this same policy (they shared the whole struct).
# ============================================================================
# kind: NetworkPolicy — firewall L4 declarativo (estilo k8s NetworkPolicy)
# kind: NetworkPolicy — declarative L4 firewall (k8s NetworkPolicy style)
#
# PT: A direcção está em `spec.direction` (ingress|egress) e o alcance em
# `spec.scope`. Cada documento é o ESTADO DESEJADO de UMA direcção de UM
# alvo: aplicar SUBSTITUI as regras dessa direcção (allowlist + default),
# deixando a outra intacta — por isso dois documentos para o mesmo
# (alvo, direcção) são recusados. `scope: container` (default) actua num
# workload; `scope: network` é uma política de SAÍDA de uma rede inteira
# (só egress); `scope: vm` é o firewall do nó onde a VM corre (ADR-0052).
# Só actua em workloads numa rede SDN custom.
# EN: Direction is `spec.direction` (ingress|egress) and reach is `spec.scope`.
# Each document is the DESIRED STATE of ONE direction of ONE target: applying
# it REPLACES that direction's rules (allowlist + default), leaving the other
# untouched — so two documents for the same (target, direction) are refused.
# `scope: container` (default) acts on a workload; `scope: network` is a whole
# network's EGRESS policy (egress only); `scope: vm` is the firewall of the
# node the VM runs on (ADR-0052). It only acts on workloads on a custom SDN.
#
# PT: NOTA: `kind: Ingress` já NÃO é firewall — é hoje o Ingress L7/HTTP estilo
# k8s (host/path→backend, ver examples/gateway/ingress.yaml). E `kind: Egress`
# foi REMOVIDO — o load RECUSA-o, nomeando `direction: egress` como substituta:
# nenhum modelo conhecido separa entrada de saída em TIPOS, por isso a direcção
# é um campo, com uma struct, um validador e um apply só.
# EN: NOTE: `kind: Ingress` is NO LONGER firewall — it is now the k8s-shaped L7/HTTP
# Ingress (host/path→backend, see examples/gateway/ingress.yaml). And `kind: Egress`
# was REMOVED — the load REFUSES it, naming `direction: egress` as the replacement:
# no known model splits inbound from outbound into TYPES, so direction is a field,
# with one struct, one validator and one apply.
#
# PT: Campos spec: direction, scope, target, defaultPolicy, rules[], allowCidrs,
# fqdnAllowlist, rateLimit. Campos por-regra (scope container/vm): proto,
# port, action, from, fromWorkload, to, toWorkload, note. allowCidrs/
# fqdnAllowlist/rateLimit só para scope: network.
# EN: spec fields: direction, scope, target, defaultPolicy, rules[], allowCidrs,
# fqdnAllowlist, rateLimit. Per-rule fields (scope container/vm): proto, port,
# action, from, fromWorkload, to, toWorkload, note. allowCidrs/fqdnAllowlist/
# rateLimit are for scope: network only.
#
# PT: Aplicar: delonix stack apply -f examples/networking/networkpolicy.yaml
# EN: Apply: delonix stack apply -f examples/networking/networkpolicy.yaml
# ============================================================================
# ── 0. Recursos de apoio INLINE — redes e workloads que as políticas protegem
# Supporting resources INLINE — networks and workloads the policies protect
---
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 # PT: a firewall L4 só actua em workloads na SDN | EN: the L4 firewall only acts on workloads on the SDN
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 (scope container) — cada forma de regra ingress ─────
# INGRESS allowlist (scope container) — every ingress-side rule form
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: db-inbound }
spec:
direction: ingress # PT: ingress (entrada) | egress (saída) | EN: ingress (inbound) | egress (outbound)
scope: container # PT: container (default): `target` é um workload | EN: container (default): `target` is a workload name
target: db # PT: o workload que a política protege | EN: the workload the policy protects
defaultPolicy: deny # PT: allow | deny quando nenhuma regra casa (default scope container: deny) | EN: allow | deny when no rule matches (default for scope container: deny)
rules:
- proto: tcp # PT: tcp | udp | any (default any) | EN: tcp | udp | any (default any)
port: "5432" # PT: uma porta | EN: a single port
from: 10.219.0.0/16 # PT: CIDR de origem | EN: source CIDR
action: allow # PT: allow (default) | deny | EN: allow (default) | deny
note: postgres-from-sdn # PT: rótulo livre, mostrado nas listagens | EN: free-text label, shown in listings
- proto: tcp
port: "5432"
fromWorkload: app # PT: origem POR NOME de workload (resolvida ao endereço SDN no apply; sobrevive a restarts). Exclusiva com from/to. | EN: source BY WORKLOAD NAME (resolved to its SDN address at apply; survives restarts). Exclusive with from/to.
note: postgres-from-app
- proto: udp
port: "8000-8100" # PT: um intervalo de portas n-m | EN: a port range n-m
from: 10.219.5.0/24
note: udp-range
- proto: any # PT: qualquer protocolo | EN: any protocol
port: "*" # PT: qualquer porta | EN: any port
from: 10.219.9.9/32
action: deny # PT: um deny explícito, avaliado pela ordem das regras | EN: an explicit deny, evaluated by rule order
note: block-this-host
# ── 2. INGRESS com defaultPolicy allow — só os denies listados actuam ────────
# 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 # PT: tudo o que não está listado é aceite | EN: everything not listed is accepted
rules:
- port: "6379" # PT: proto omitido = any | EN: proto omitted = any
from: 203.0.113.0/24
action: deny
note: no-external-redis
# ── 3. EGRESS allowlist de um workload — CIDR, nome de workload e wildcard ───
# EGRESS allowlist for a workload — 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 # PT: CIDR de destino (o par egress de `from`) | EN: destination CIDR (egress-side counterpart of `from`)
note: https-anywhere
- proto: tcp
port: "5432"
toWorkload: db # PT: destino POR NOME de workload (par egress de fromWorkload) | EN: destination BY WORKLOAD NAME (egress counterpart of fromWorkload)
note: postgres-to-db
# ── 4. EGRESS mínimo — sem regras, default deny → sem saída possível ─────────
# Minimal EGRESS — no rules, default deny → no outbound possible
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: cache-outbound-locked }
spec:
direction: egress
target: cache
defaultPolicy: deny
# ── 5. EGRESS por-REDE (scope: network, só egress) — `target` é uma REDE ─────
# PER-NETWORK EGRESS (scope: network, egress only) — `target` is a NETWORK
#
# PT: Allowlist de CIDR + allowlist de FQDN (aprendida ao vivo do DNS) + rate-limit
# L4 global do ingress rootless. É a forma declarativa do `delonix net egress`.
# EN: CIDR allowlist + FQDN allowlist (learnt live from DNS) + global L4 rate limit
# of the rootless ingress. The declarative form of `delonix net egress`.
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: sdn-locked-egress }
spec:
direction: egress
scope: network # PT: container (default) | network | EN: container (default) | network
target: sdn-locked # PT: nome de uma REDE, não de um workload | EN: a NETWORK name, not a workload
defaultPolicy: deny # PT: allowCidrs/fqdnAllowlist só fazem sentido com deny | EN: allowCidrs/fqdnAllowlist only make sense with deny
allowCidrs: # PT: destinos permitidos além do DNS | EN: destinations allowed besides DNS
- 10.0.0.0/8
- 198.51.100.0/24
fqdnAllowlist: # PT: hostnames permitidos (e `*.host`), via DNS-snooping | EN: hostnames allowed (and `*.host`), via DNS snooping
- registry.example.com
- updates.example.org
rateLimit: # PT: guarda L4 GLOBAL do ingress rootless (não por rede) | EN: GLOBAL L4 guard of the rootless ingress (not per network)
connRate: 100 # PT: novas conexões por segundo | EN: new connections per second allowed
connMax: 2000 # PT: conexões concorrentes máximas | EN: maximum concurrent connections
# ── 6. EGRESS por-REDE totalmente aberto (defaultPolicy allow) ───────────────
# PER-NETWORK EGRESS fully open (defaultPolicy allow)
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: sdn-demo-egress-open }
spec:
direction: egress
scope: network
target: sdn-demo
defaultPolicy: allow
# ── 7. scope: vm — o firewall do nó onde a VM corre (ADR-0052) ───────────────
# scope: vm — the firewall of the node the VM runs on (ADR-0052)
#
# PT: Hoje é um nó Proxmox; uma VM libvirt/Cloud Hypervisor recusa a política com
# a razão. Precisa de um alvo Proxmox registado (DELONIX_PROXMOX_URL/_NODE +
# credencial) e do firewall do DATACENTER já ligado (o motor nunca liga esse
# interruptor). Regras avaliadas POR ORDEM no nó (first match wins), acima de
# qualquer regra posta à mão, que nunca é tocada.
# EN: Today that is a Proxmox node; a libvirt/Cloud Hypervisor VM refuses the policy
# with the reason. Needs a registered Proxmox target (DELONIX_PROXMOX_URL/_NODE
# + credential) and the DATACENTER firewall already on (the engine never flips
# that switch). Rules are evaluated IN ORDER on the node (first match wins),
# above any hand-added rule, which is never touched.
---
apiVersion: compute.delonix.io/v1alpha1
kind: VirtualMachine
metadata: { name: web-vm }
spec:
provider: { type: proxmox }
disk: "local-lvm:8"
vcpus: 2
memory: 2G
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: web-vm-in }
spec:
scope: vm
target: web-vm
direction: ingress
defaultPolicy: deny
rules:
- { proto: tcp, port: "22", from: 10.0.0.0/8, note: "ssh from the admin network" }
- { proto: tcp, port: "443" }
- { port: "53", from: 10.0.0.0/8 } # PT: qualquer protocolo + uma porta = TCP e UDP | EN: any protocol + a port = TCP and UDP
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkPolicy
metadata: { name: web-vm-out }
spec:
scope: vm
target: web-vm
direction: egress
defaultPolicy: allow
rules:
- { proto: tcp, port: "25", action: deny, note: "no direct mail out" }
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 (estilo Gateway API)
# kind: HTTPRoute — declarative L7/HTTP reverse proxy (Gateway API style)
#
# PT: Roteia por Host e/ou prefixo de path para containers backend. O proxy
# (hyper embutido) corre DENTRO do netns do holder — alcança os backends pelo
# IP interno da SDN — e publica as portas de entrada no host. O TLS termina no
# proxy (self-signed por omissão, ou um par PEM de um `kind: Secret`). TODOS
# os documentos HTTPRoute (e Ingress) do manifesto compõem UM só proxy: os
# listeners juntam-se por porta (TLS ganha na colisão), as rotas concatenam-se
# e há UM único certificado (sem SNI). `kind: Ingress` é a forma k8s do MESMO
# proxy (Compat→HTTPRoute, ver ingress.yaml); o firewall L4 é `kind:
# NetworkPolicy` (ver ../networking/networkpolicy.yaml).
# EN: Routes by Host and/or path prefix to backend containers. The proxy (embedded
# hyper) 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
# (self-signed by default, or a PEM pair from a `kind: Secret`). ALL HTTPRoute
# (and Ingress) documents of a manifest compose into ONE proxy: listeners are
# merged by port (TLS wins on collision), routes are concatenated, and there is
# a SINGLE certificate (no SNI). `kind: Ingress` is the k8s spelling of the SAME
# proxy (Compat→HTTPRoute, see ingress.yaml); the L4 firewall is `kind:
# NetworkPolicy` (see ../networking/networkpolicy.yaml).
#
# PT: Os backends TÊM de ser `kind: Container` com IP numa rede SDN custom (não
# --net host/none): cada container mantém o seu nome e IP, por isso a rota
# nomeia-o directamente.
# EN: Backends MUST be `kind: Container` with an IP on a custom SDN network (not
# --net host/none): each container keeps its own name and IP, so a route names
# it directly.
#
# PT: Aplicar: delonix stack apply -f examples/gateway/httproute.yaml
# EN: Apply: delonix stack apply -f examples/gateway/httproute.yaml
# ============================================================================
# ── 1. Backends — rede SDN + containers que as rotas nomeiam ─────────────────
# Backends — SDN network + containers the routes name
---
apiVersion: networking.delonix.io/v1alpha1
kind: Network
metadata:
name: appnet
spec:
driver: bridge # PT: rede custom: dá IP interno aos backends | EN: custom network: gives backends an internal IP
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: web
spec:
image: "nginx:1.27-alpine"
network: appnet # PT: numa rede custom, para o proxy o alcançar por IP | EN: on a custom network, so the proxy reaches it by IP
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: api
spec:
image: "hashicorp/http-echo:latest"
command: ["-listen=:3000", "-text=hello from api"] # PT: serve em :3000 | EN: serves on :3000
network: appnet
---
apiVersion: compute.delonix.io/v1alpha1
kind: Container
metadata:
name: admin
spec:
image: "hashicorp/http-echo:latest"
command: ["-listen=:9000", "-text=admin"] # PT: serve em :9000 | EN: serves on :9000
network: appnet
# ── 2. selfSigned (default) — rota principal, dois listeners ─────────────────
# selfSigned (default) — main route, two listeners
---
apiVersion: gateway.delonix.io/v1alpha1
kind: HTTPRoute
metadata:
name: loja
spec:
entrypoints:
- { port: 80 } # PT: listener HTTP simples (tls default false) | EN: plain HTTP listener (tls defaults to false)
- { port: 443, tls: true } # PT: termina TLS aqui (exige spec.tls) | EN: TLS terminates here (requires spec.tls)
tls:
mode: selfSigned # PT: selfSigned (default, cert gerado para os hosts) | secretRef | EN: selfSigned (default, cert generated for the hosts) | secretRef
rules:
- host: loja.exemplo.ao # PT: casa pelo header Host: (omitir = qualquer Host) | EN: matched against the Host header (omit = any Host)
paths:
- path: / # PT: prefixo de path; o prefixo mais longo ganha | EN: path prefix; the longest prefix wins
backend: { service: web, port: 80 }
- path: /api # PT: prefixos mais específicos ganham | EN: more specific prefixes win
backend: { service: api, port: 3000 }
# ── 3. secretRef — certificado BYO de um kind: Secret ────────────────────────
# secretRef — BYO certificate from a kind: Secret
---
# PT: Par PEM sob tls.crt / tls.key (tls_crt / tls_key também servem). Substitui
# os placeholders por um par real antes de servir tráfego real.
# EN: PEM pair 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: painel-cert
spec:
stringData:
tls.crt: "-----BEGIN CERTIFICATE-----\nplaceholder\n-----END CERTIFICATE-----"
tls.key: "-----BEGIN PRIVATE KEY-----\nplaceholder\n-----END PRIVATE KEY-----"
---
apiVersion: gateway.delonix.io/v1alpha1
kind: HTTPRoute
metadata:
name: painel
spec:
entrypoints:
- { port: 8080 } # PT: listener HTTP extra, junta-se aos de cima | EN: extra HTTP listener, merged with the ones above
- { port: 8443, tls: true }
tls:
mode: secretRef # PT: usa o certificado do Secret em vez de um self-signed | EN: use the Secret's certificate instead of a self-signed one
secretRef: painel-cert # PT: kind: Secret com tls.crt / tls.key | EN: kind: Secret holding tls.crt / tls.key
rules:
- host: admin.exemplo.ao
paths:
- path: /
backend: { service: admin, port: 9000 }
# ── 4. catch-all — regra sem host (qualquer Host), sem entrypoints ───────────
# catch-all — a rule with no host (any Host), no entrypoints
---
# PT: Sem `host` casa QUALQUER Host (catch-all por path); `path` omitido = `/`.
# Sem `entrypoints`: default porta 80 (+443 com TLS se `tls` estivesse posto).
# EN: No `host` matches ANY Host (a catch-all by path); `path` omitted defaults to
# `/`. No `entrypoints`: 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 }
Campos de spec — 5spec fields — 5
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
entrypoints | array<Entrypoint> | — | [] | Entry points (ports where the proxy listens). Default: `[{port: 80}]`, plus an implicit `{port: 443, tls: true}` if `spec.tls` is defined. |
hosts | array<string> | — | — | Where to publish this route's host names, so they RESOLVE without editing any `hosts` file by hand (ADR-0046). Only `host` exists so far: each rule `host` gets `127.0.0.1` in a delimited block of the operator host's `/etc/hosts` (needs root, refused otherwise). `containers` and `guest` are planned (ADR-0047 for the first). The PORT is the route's entrypoint, which a hosts file cannot carry. |
pool | string | — | — | `kind: IPPool` this route takes its address from (ADR-0046 D3). The route holds ONE address of the pool for as long as it is declared, and every listener of it is reachable there instead of on loopback; `hosts: [host]` then points the name at that address. The address must already be on an interface of this host (`announce: local`) — the apply says so and stops when it is not. |
rules | array<RouteRule> | — | [] | Routing rules (by Host and/or path prefix). Required and non-empty. |
tls | TlsSpec | — | — | TLS configuration (optional). Without it, the proxy only serves HTTP. |
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 — expõe UMA porta TCP local à internet pública (via túnel)
# kind: Gateway — exposes ONE local TCP port to the public internet (via a tunnel)
#
# PT: Através de um provider externo (pinggy/ngrok/cloudflare). De propósito
# single-purpose: faz só o transporte de saída. Para publicar VÁRIOS backends
# atrás de um só URL público, aponta `localPort` para a porta do proxy do
# `kind: HTTPRoute` (ver httproute.yaml) — esse é que faz o routing L7 por
# Host/path; o firewall L4 é `kind: NetworkPolicy`. Cada provider é uma CLI
# real já instalada: pinggy (zero binário extra, só `ssh`), ngrok (precisa do
# agente `ngrok` no PATH), cloudflare (precisa de `cloudflared` no PATH).
# EN: Through a third-party provider (pinggy/ngrok/cloudflare). Deliberately
# single-purpose: it only does the outbound transport. To publish MANY backends
# behind one public URL, point `localPort` at the `kind: HTTPRoute` proxy's port
# (see httproute.yaml) — that is what does the L7 Host/path routing; the L4
# firewall is `kind: NetworkPolicy`. Each provider is a real, already-installed
# CLI: pinggy (zero extra binary, just `ssh`), ngrok (needs the `ngrok` agent on
# PATH), cloudflare (needs `cloudflared` on PATH).
#
# PT: `token` e `tokenSecretRef` são duas formas da MESMA credencial: usa UMA por
# documento. Só pode viver UM túnel ngrok de cada vez (a API local do agente
# tem porta fixa).
# EN: `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).
#
# PT: Aplicar: delonix stack apply -f examples/gateway/gateway.yaml
# Equivalente num comando, sem manifesto (pinggy é o provider default):
# delonix net tunnel expose 80
# EN: Apply: delonix stack apply -f examples/gateway/gateway.yaml
# One-shot equivalent, no manifest (pinggy is the default provider):
# delonix net tunnel expose 80
# ============================================================================
# ── 1. Mínimo — pinggy free, sem conta e sem binário extra ───────────────────
# Minimal — pinggy free tier, no account and no extra binary
---
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata:
name: public-min
spec:
provider: pinggy # PT: forma escalar = seleccionar sem opções | EN: scalar form = select with no options
localPort: 80 # PT: porta local a expor (ex. o proxy HTTPRoute, ou uma porta publicada) | EN: local port to expose (e.g. the HTTPRoute proxy, or a published port)
# ── 2. pinggy com pro token literal (funciona, mas prefere tokenSecretRef) ───
# 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:
type: pinggy # PT: pinggy | ngrok | cloudflare | EN: pinggy | ngrok | cloudflare
spec: # PT: o que só esse serviço entende | EN: what only that service understands
token: "REPLACE-WITH-PINGGY-PRO-TOKEN" # PT: token literal — prefere tokenSecretRef num manifesto | EN: literal token — prefer tokenSecretRef in a manifest
localPort: 8080
# ── 3. Secret de token — stringData sob spec, chave `token` ──────────────────
# Token Secret — stringData under spec, key `token`
---
# PT: O provider lê a credencial da chave `token` deste Secret (valor placeholder).
# EN: The provider reads the credential from this Secret's `token` key (placeholder value).
apiVersion: core.delonix.io/v1alpha1
kind: Secret
metadata:
name: tunnel-token
spec:
stringData:
token: "REPLACE-WITH-PROVIDER-TOKEN"
# ── 4. ngrok com hostname reservado e o token lido de um Secret ──────────────
# ngrok with a reserved hostname and the token read from a Secret
---
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata:
name: public-ngrok
spec:
provider:
type: ngrok
spec:
hostname: demo.ngrok.app # PT: hostname custom/reservado — ngrok usa-o como --url | EN: custom/reserved hostname — ngrok uses it as --url
tokenSecretRef: tunnel-token # PT: kind: Secret cuja chave `token` tem o authtoken | EN: kind: Secret whose `token` key holds the authtoken
localPort: 8080
# ── 5. cloudflare NAMED tunnel — cloudflared tunnel run --token ... ──────────
# cloudflare NAMED tunnel — cloudflared tunnel run --token ...
---
# PT: Corre um túnel que já criaste. `hostname` é só informativo — a rota vive no
# dashboard da Cloudflare. `insecureSkipTlsVerify` salta a verificação do cert
# self-signed do backend LOCAL (origin https://localhost:<localPort>); o URL
# público tem sempre um certificado real.
# EN: Runs a tunnel you already created. `hostname` is informational only — the route
# lives in the Cloudflare dashboard. `insecureSkipTlsVerify` skips verification of
# the LOCAL backend's self-signed cert (origin https://localhost:<localPort>); the
# public URL always has a real certificate.
apiVersion: gateway.delonix.io/v1alpha1
kind: Gateway
metadata:
name: public-cloudflare
spec:
provider:
type: cloudflare
spec:
hostname: app.example.com
tokenSecretRef: tunnel-token
localPort: 8443
insecureSkipTlsVerify: true # PT: só afecta o backend LOCAL; no-op em pinggy | EN: only affects the LOCAL backend; no-op for pinggy
# ── 6. cloudflare quick tunnel — sem token (URL efémero trycloudflare.com) ───
# 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
Campos de spec — 6spec fields — 6
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
hostname | string | — | — | Custom/reserved hostname — provider-dependent support (see module doc). For `cloudflare` this is informational only: it does not create the route, it just labels the URL this tunnel is expected to answer on — the route itself lives in the Cloudflare dashboard for that tunnel. |
insecureSkipTlsVerify | boolean | — | false | Skip TLS verification when the tunnel connects to the LOCAL backend (a self-signed cert on `localhost:<localPort>`) — never affects the public tunnel URL, which every provider here always serves over a real, provider-issued TLS cert. No-op for `pinggy` (it forwards raw TCP and never inspects what is behind it). |
localPort | integer | simyes | — | — |
provider | string | object | simyes | — | — |
token | string | — | — | Literal provider token (pinggy pro token / ngrok authtoken / a cloudflare NAMED tunnel's token, from `cloudflared tunnel token <name>` or the Zero Trust dashboard). Prefer `tokenSecretRef` for anything checked into a manifest. |
tokenSecretRef | string | — | — | Pull the token from a `kind: Secret`'s `token` key — same convention as `storage`'s `--password-secret`. |
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 — construir a imagem de uma aplicação a partir do código-fonte
# com Cloud Native Buildpacks, sem Dockerfile
# kind: App — build an application image from source with Cloud Native
# Buildpacks, no Dockerfile
#
# PT: `image` é o ÚNICO campo obrigatório — a tag que a imagem construída recebe
# no ImageStore LOCAL. `source` (default ".") é relativo ao directório onde
# CORRES o comando (não a este ficheiro), a mesma convenção do `build.context`
# de `kind: Image`. `builder` escolhe como o stack é construído: `auto`
# (detecta o stack — go.mod, package.json, requirements.txt, … — e usa o
# builder Paketo), `heroku` (a família Heroku), ou a referência de uma imagem
# builder própria — e UM builder próprio TORNA `runImage` OBRIGATÓRIO (o motor
# não lê o `builder.toml` de um builder alheio para adivinhar a sua run image;
# recusar é a resposta honesta). Sem cache de build: cada apply reconstrói.
# EN: `image` is the ONLY required field — the tag the built image gets in the
# LOCAL ImageStore. `source` (default ".") is relative to where you RUN the
# command (not to this file), the same convention `kind: Image`'s
# `build.context` uses. `builder` selects how the stack is built: `auto`
# (detect the stack — go.mod, package.json, requirements.txt, … — and use the
# Paketo builder), `heroku` (the Heroku family), or a custom builder image
# reference — and a custom builder MAKES `runImage` REQUIRED (the engine does
# not read a custom builder's `builder.toml` to guess its run image; refusing
# is the honest answer). No build cache: every apply rebuilds.
#
# PT: Aplicar: delonix stack apply -f examples/artifact/app.yaml
# EN: Apply: delonix stack apply -f examples/artifact/app.yaml
# ============================================================================
# ── 1. auto — stack detectado automaticamente (builder Paketo) ───────────────
# auto — auto-detected stack (Paketo builder)
---
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-auto
labels:
app: shop
spec:
source: . # PT: directório a construir; relativo ao CWD, não a este ficheiro | EN: directory to build; relative to CWD, not this file
builder: auto # PT: auto | heroku | <ref de imagem builder> (default auto) | EN: auto | heroku | <builder image ref> (default auto)
image: shop-auto:latest # PT: OBRIGATÓRIO — tag da imagem construída no store local | EN: REQUIRED — tag of the built image in the local store
# ── 2. heroku — a família de builders Heroku ─────────────────────────────────
# heroku — the Heroku builder family
---
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-heroku
spec:
source: .
builder: heroku
image: shop-heroku:latest
# ── 3. custom — uma imagem builder própria (exige runImage) ──────────────────
# custom — a custom builder image (requires runImage)
# PT: `runImage` é obrigatório aqui (omiti-lo é erro) e sem sentido com
# auto/heroku — o motor só o lê neste caso.
# EN: `runImage` is required here (omitting it is an error) and meaningless with
# auto/heroku — the engine only reads it in this case.
---
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-custom
spec:
source: . # PT: qualquer directório com o código (ex. ./services/shop); apply (e --dry-run) recusa um inexistente | EN: any directory holding the source (e.g. ./services/shop); apply (and --dry-run) refuse one that does not exist
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 — só a tag da imagem é obrigatória ───────────────────────────
# defaults — only the image tag is mandatory
# PT: source assume ".", builder assume "auto".
# EN: source defaults to ".", builder to "auto".
---
apiVersion: artifact.delonix.io/v1alpha1
kind: App
metadata:
name: shop-defaults
spec:
image: shop-defaults:latest
Campos de spec — 4spec fields — 4
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
builder | string | — | "auto" | `"auto"` (detect the stack, use the Paketo builder), `"heroku"`, or an explicit builder image reference — the last one REQUIRES `runImage` (this engine does not introspect a custom builder's own `builder.toml` to discover its run image; refusing is the honest answer, not a guess). |
image | string | simyes | — | Tag the built image gets in the LOCAL `ImageStore` once the build succeeds. |
runImage | string | — | — | — |
source | string | — | "." | Directory to build (Compose-style relative-to-CWD default, same convention `kind: Image`'s `build.context` already uses — not the manifest file's own directory). |
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 workloads seleccionado por label
# kind: Service (ADR-0032) — a set of workloads picked by label
#
# PT: `type: DNS` (default) publica o conjunto como MÚLTIPLOS registos DNS `A`
# sob <nome>.<namespace>.delonix.internal, em ordem rotativa a cada consulta
# (round-robin) — sem VIP, sem dataplane L4, sem daemon. `type: VirtualIP`
# (ADR-0066) pega UM endereço do pool de VIP do operador; o nome resolve para
# esse endereço e o holder faz DNAT de cada ligação nova para um backend
# pronto. A membership é recomputada dentro de um TTL do índice DNS (~2s): um
# workload acrescentado, removido ou relabelado aparece sem reaplicar.
# EN: `type: DNS` (default) publishes the set as MULTIPLE DNS `A` records under
# <name>.<namespace>.delonix.internal, in rotating order (round-robin) — no
# VIP, no L4 dataplane, no daemon. `type: VirtualIP` (ADR-0066) takes ONE
# address from the operator's VIP pool; the name resolves to that address and
# the holder DNATs each new connection to a ready backend. Membership is
# recomputed within a DNS-index TTL (~2s): a workload added, removed or
# relabelled shows up without re-applying.
#
# PT: Campos spec: selector.matchLabels (AND lógico), port (porta do CONTAINER,
# nunca de host/VIP), type, vip, readiness, publish. Um selector vazio/sem
# match resolve para conjunto vazio — não é erro, mas o apply avisa.
# EN: spec fields: selector.matchLabels (logical AND), port (the CONTAINER-side
# port, never a host/VIP port), type, vip, readiness, publish. An empty/no-match
# selector resolves to an empty set — not an error, but apply warns.
#
# PT: Aplicar: delonix stack apply -f examples/networking/service.yaml
# EN: Apply: delonix stack apply -f examples/networking/service.yaml
# ============================================================================
# ── 0. Recursos de apoio INLINE — a rede e os workloads que os Services apanham
# Supporting resources INLINE — the network and workloads the Services pick
---
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 } # PT: labels viajam pelo Workload → container rotulado | EN: labels flow through Workload → labelled container
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 # PT: um namespace diferente de `default` | EN: a namespace other than `default`
labels: { app: api }
spec:
type: container
container: { image: "caddy:alpine", network: appnet }
# ── 1. selector de uma label, namespace `default` ────────────────────────────
# single-label selector, namespace `default`
#
# PT: Clientes resolvem web.default.delonix.internal e recebem web-1 e web-2 em rotação.
# EN: 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 # PT: toda a label listada tem de corresponder | EN: every listed label must match
port: 80 # PT: porta CONTAINER-side onde os membros escutam | EN: container-side port the members listen on
# ── 2. selector multi-label — estreita o conjunto só ao track estável (web-1) ─
# multi-label selector — narrows the set to the stable track only (web-1)
---
apiVersion: networking.delonix.io/v1alpha1
kind: Service
metadata:
name: web-stable
spec: # PT: namespace omitido = `default` | EN: namespace omitted = `default`
selector:
matchLabels:
app: web
track: stable
port: 80
# ── 3. Service noutro namespace — resolve como api.team-a.delonix.internal ────
# Service in another namespace — resolves as api.team-a.delonix.internal
#
# PT: O isolamento de namespace aplica-se ao DNS: outros namespaces dão NXDOMAIN
# (excepto os serviços em `default`).
# EN: 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
# ── 4. type: VirtualIP (ADR-0066) — UM endereço do pool de VIP do operador ───
# type: VirtualIP (ADR-0066) — ONE address from the operator's VIP pool
#
# PT: O nome resolve para esse endereço e o holder faz DNAT de cada ligação nova
# para um backend PRONTO (numgen aleatório). Pronto = a porta aceita um TCP
# connect do holder E (se declarado) está healthy. Pool por
# `delonix network vip-pool set`.
# EN: The name resolves to that address and the holder DNATs each new connection
# to a READY backend (random numgen). Ready = the port accepts a TCP connect
# from the holder AND (if declared) is healthy. Pool set via
# `delonix network vip-pool set`.
---
apiVersion: networking.delonix.io/v1alpha1
kind: Service
metadata:
name: web-vip
namespace: default
spec:
selector:
matchLabels:
app: web # PT: mesmos backends da #1: web-1 e web-2 | EN: same backends as #1: web-1 and web-2
port: 80 # PT: porta CONTAINER-side (= porta do VIP; uma só em v1) | EN: container-side port (= the VIP port; a single one in v1)
type: VirtualIP # PT: DNS (default) | VirtualIP | EN: DNS (default) | VirtualIP
vip: 10.80.0.5 # PT: IPv4 pontilhado dentro do pool; omitir = IPAM escolhe | EN: dotted IPv4 inside the pool; omit = IPAM picks
readiness: # PT: knobs da sonda TCP (todos opcionais) | EN: knobs of the TCP readiness probe (all optional)
tcp:
intervalSeconds: 2 # PT: default 2 s | EN: default 2 s
timeoutMilliseconds: 500 # PT: default 500 ms | EN: default 500 ms
successThreshold: 1 # PT: sucessos para admitir; default 1 | EN: successes to admit; default 1
failureThreshold: 2 # PT: falhas para ejectar; default 2 | EN: failures to eject; default 2
publish: [8080, 8443] # PT: portas de host que levam aos backends do VIP (127.0.0.1 salvo DELONIX_PUBLISH_ADDR) | EN: host ports that lead to the VIP's backends (127.0.0.1 unless DELONIX_PUBLISH_ADDR)
Campos de spec — 6spec fields — 6
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
port | integer | simyes | — | The CONTAINER port every matched workload is expected to listen on — mirrors the "always container-side, post-DNAT" convention `net ingress allow`'s port already uses. With `type: VirtualIP` it is also the VIP's port: one port in v1 (ADR-0066 open question 1), and no host-side port. |
publish | array | — | — | `type: VirtualIP` only: host ports that lead to the VIP's backends (ADR-0066 D6), bound like a container's `-p` (`127.0.0.1` unless `DELONIX_PUBLISH_ADDR` widens it). |
readiness | ServiceReadiness | — | — | `type: VirtualIP` only: the knobs of the TCP readiness check that always runs for each backend. |
selector | ServiceSelector | simyes | — | — |
type | ServiceTypeSpec | — | — | `DNS` (default, ADR-0032) or `VirtualIP` (ADR-0066). |
vip | string | — | — | `type: VirtualIP` only: the address to hold, inside the node's VIP pool. Without it the IPAM picks one. |
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
# kind: NetworkAccessRule — ONE INCREMENTAL firewall rule per document
#
# PT: Ao contrário de `kind: NetworkPolicy`, que SUBSTITUI o estado inteiro de uma
# direcção a cada apply (e recusa dois documentos para o mesmo alvo+direcção),
# várias NetworkAccessRule para o MESMO container ACUMULAM — cada uma retirável
# sozinha: tira-a do manifesto e reaplica com --prune e só essa é retirada; a
# outra fica intacta. Reaplicar o mesmo documento substitui só a regra que ele
# possui ("o último comando ganha" para o mesmo match). Só actua em workloads
# numa rede SDN custom.
# EN: Unlike `kind: NetworkPolicy`, which REPLACES a whole direction's state on each
# apply (and refuses two documents for the same target+direction), several
# NetworkAccessRule for the SAME container ACCUMULATE — each removable on its
# own: drop it from the manifest and re-apply with --prune and only that one is
# retired; the other stays. Re-applying the same document replaces only the rule
# it owns ("the last command wins" for the same match). It only acts on workloads
# on a custom SDN network.
#
# PT: Campos spec: target*, direction*, port*, action, proto, from (* = obrigatório).
# A identidade da regra é o metadata.name. É o primitivo declarativo
# incremental (ADR-0028); não substitui `net ingress`/`net egress` e não tem
# verbo de CLI próprio — aplica-se como qualquer outro Kind.
# EN: spec fields: target*, direction*, port*, action, proto, from (* = required).
# The rule's identity is metadata.name. It is the incremental declarative
# primitive (ADR-0028); it does not replace `net ingress`/`net egress` and has
# no CLI verb of its own — it is applied like any other Kind.
#
# PT: Aplicar: delonix stack apply -f examples/networking/networkaccessrule.yaml
# EN: Apply: delonix stack apply -f examples/networking/networkaccessrule.yaml
# ============================================================================
# ── 0. Recursos de apoio INLINE — a rede e o workload alvo ───────────────────
# Supporting resources INLINE — the network and the target workload
---
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. todos os campos explícitos — allow TCP/8080 para `web` de um CIDR ─────
# 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 # PT: o workload a que a regra se aplica | EN: the workload the rule applies to
direction: ingress # PT: ingress (tráfego PARA o workload) | egress (DELE) | EN: ingress (traffic TO the workload) | egress (FROM it)
action: allow # PT: allow (aceita, default) | deny (dropa) | EN: allow (accept, default) | deny (drop)
proto: tcp # PT: tcp | udp | any (default any) | EN: tcp | udp | any (default any)
port: "8080" # PT: uma porta, um intervalo "n-m", ou "*" (qualquer) | EN: a port, a range "n-m", or "*" (any)
from: 10.0.0.0/24 # PT: CIDR da outra ponta (origem no ingress); omitir = qualquer | EN: CIDR of the other end (source on ingress); omit = anywhere
# ── 2. acumula com a regra 1 no mesmo alvo — 2ª regra ingress independente ───
# accumulates with rule 1 on the same target — a 2nd independent ingress rule
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkAccessRule
metadata: { name: web-metrics-from-monitoring }
spec:
target: web # PT: o MESMO alvo — acumula, não substitui | EN: the SAME target — accumulates, does not replace
direction: ingress
proto: tcp
port: "9090"
from: 10.1.0.0/24
# ── 3. intervalo de portas, UDP, defaults de action (allow) e from (qualquer)
# 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. deny explícito de uma origem abusiva, qualquer protocolo e porta ──────
# 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` nomeia o CIDR de DESTINO que o workload pode alcançar ─
# 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. forma mínima — só os campos obrigatórios (direction + port + target) ──
# 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"
Campos de spec — 6spec fields — 6
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
action | string | — | — | `allow` (accept) or `deny` (drop). Default `allow`. |
direction | string | simyes | — | `ingress` (traffic TO the container) or `egress` (FROM it). |
from | string | — | — | CIDR of the other end (source on ingress, destination on egress). Empty/omitted = anywhere. |
port | string | simyes | — | Port, a range `n-m`, or `*` (any). |
proto | string | — | — | `tcp`/`udp`/`any`. Default `any`. |
target | string | simyes | — | Container this rule applies to. Must be on the SDN — a `--net host`/`none` container has no firewall to govern. |
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.
# ============================================================================
# kind: RuntimePolicy — o tecto de admissão deste nó (M04, ADR-0026)
# kind: RuntimePolicy — this node's admission ceiling (M04, ADR-0026)
#
# PT: O limite que ESTE host impõe ao que pode correr. Dá forma de manifesto ao
# ficheiro `<root>/policy.json`, o MESMO que `container run`/`vm create` já
# consultam antes de criar o que quer que seja. Cada campo arranca em «sem
# opinião», por isso uma política só declara o que quer restringir.
# EN: The ceiling THIS host puts on what may run. It gives a manifest form to the
# file `<root>/policy.json`, the SAME one `container run`/`vm create` already
# check before anything is created. Every field starts at "no opinion", so a
# policy states only what it wants to restrict.
#
# PT: SINGLETON de nó. Seja qual for o `metadata.name`, o apply escreve sempre o
# ÚNICO `policy.json` — um manifesto declara no máximo UM documento
# RuntimePolicy (um segundo é recusado por `stack validate`/`apply`).
# EN: A node-wide SINGLETON. Whatever `metadata.name` says, apply always writes
# the ONE `policy.json` — a manifest declares at most ONE RuntimePolicy
# document (a second is refused by `stack validate`/`apply`).
#
# PT: Converge como qualquer Kind (um campo mudado é aplicado de facto), mas NÃO
# desce sozinho: remover este documento e correr `stack apply --prune` (ou
# `stack destroy`) deixa o tecto como estava. A única forma de o baixar é
# `delonix policy unset` (pede confirmação, ou `--force`).
# EN: It converges like any Kind (a changed field really is applied), but does
# NOT come down on its own: removing this document and running `stack apply
# --prune` (or `stack destroy`) leaves the ceiling as it was. The only way
# down is `delonix policy unset` (asks for confirmation, or `--force`).
#
# PT: Aplicar: delonix stack apply -f examples/security/runtimepolicy.yaml
# EN: Apply: delonix stack apply -f examples/security/runtimepolicy.yaml
# ============================================================================
# ── 1. o tecto do nó — todos os 13 campos, explícitos ───────────────────────
# the node ceiling — all 13 fields, made explicit
---
apiVersion: security.delonix.io/v1alpha1
kind: RuntimePolicy
metadata:
name: node-ceiling
spec:
mode: enforce # PT: `enforce` (recusa, default) ou `warn` (deixa passar e reporta) | EN: `enforce` (refuse, default) or `warn` (allow and report)
denyPrivileged: true # PT: recusa `container run --privileged` | EN: refuse `container run --privileged`
denyHostNetwork: true # PT: recusa `--net host` (o modo de rede DEFAULT deste motor) | EN: refuse `--net host` (this engine's DEFAULT network mode)
denyLatestTag: true # PT: recusa imagem de container sem tag ou com `:latest` | EN: refuse a container image with no tag or `:latest`
allowedRegistries: [ghcr.io, docker.io] # PT: só estes registos podem ser puxados; vazio = sem opinião | EN: only these registries may be pulled; empty = no opinion
workloadSecurity: baseline # PT: único perfil em v1 — recusa aquilo de que `--privileged` é feito (caps além do baseline PSS, namespaces PID/IPC do host, seccomp/apparmor unconfined, binds de host-path, devices do host, lista masked/ro custom que larga os defaults); omitido = sem opinião | EN: the only v1 profile — refuses what `--privileged` is made of (caps beyond the PSS baseline, the host PID/IPC namespaces, seccomp/apparmor unconfined, host-path binds, host devices, a custom masked/ro list that drops the defaults); omitted = no opinion
denyDevicePassthrough: true # PT: recusa `vm create --device` (VFIO PCI passthrough) — dá ao guest DMA ao hardware do host | EN: refuse `vm create --device` (VFIO PCI passthrough) — hands the guest DMA to host hardware
denyLatestVmImage: true # PT: recusa imagem de disco de VM sem tag ou `:latest` (separado de denyLatestTag: um build `--vmfile` marca `<nome>:latest`) | EN: refuse a VM disk image with no tag or `:latest` (separate from denyLatestTag: a `--vmfile` build tags `<name>:latest`)
allowedImageUrlHosts: [cloud.debian.org] # PT: hosts de onde um `vm create --url-img` qcow2 pode ser trazido; vazio = sem opinião | EN: hosts a `vm create --url-img` qcow2 may be fetched from; empty = no opinion
allowedSourcePrefixes: [10.10.0.0/16] # PT: GRANT — prefixos canónicos a.b.c.d/len que `--allow-source` pode usar; vazio = nenhum | EN: GRANT — canonical a.b.c.d/len prefixes `--allow-source` may use; empty = none
allowSourceCheckOptOut: false # PT: GRANT — true deixa um container usar `--no-source-check`; ausente = recusado | EN: GRANT — true lets a container use `--no-source-check`; absent = refused
denyPrivateEgress: true # PT: recusa egress novo de um workload para espaço privado (10/8, 172.16/12, 192.168/16, 100.64/10) pelo uplink do nó | EN: refuse new egress from a workload to private space (10/8, 172.16/12, 192.168/16, 100.64/10) through the node's uplink
allowedEgressPrefixes: [10.0.0.0/8] # PT: GRANT — destinos privados ainda alcançáveis com denyPrivateEgress ligado; vazio = nenhum | EN: GRANT — private destinations still reachable while denyPrivateEgress is on; empty = none
# PT: Em efeito: `delonix get runtimepolicies` e `delonix describe runtimepolicy node-ceiling` mostram o que está a vigorar.
# EN: In effect: `delonix get runtimepolicies` and `delonix describe runtimepolicy node-ceiling` show what is enforced.
Campos de spec — 13spec fields — 13
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
allowSourceCheckOptOut | boolean | — | false | Lets a container switch the source check off (`--no-source-check`). A GRANT: absent = refused. |
allowedEgressPrefixes | array<string> | — | [] | Private destinations still reachable while `denyPrivateEgress` is on (canonical `a.b.c.d/len`). A GRANT: empty = none. |
allowedImageUrlHosts | array<string> | — | [] | Hosts a `vm create --url-img` qcow2 may be fetched from. Empty = no opinion. |
allowedRegistries | array<string> | — | [] | Only these registries may be pulled from. Empty = no opinion. |
allowedSourcePrefixes | array<string> | — | [] | Source prefixes a container may be authorised to use (`--allow-source`). A GRANT: empty = none. |
denyDevicePassthrough | boolean | — | false | Refuse `vm create --device` (VFIO PCI passthrough). |
denyHostNetwork | boolean | — | false | Refuse `--net host`, this engine's default network mode. |
denyLatestTag | boolean | — | false | Refuse a container image reference with no tag or with `:latest`. |
denyLatestVmImage | boolean | — | false | Refuse a VM disk image reference with no tag or with `:latest`. |
denyPrivateEgress | boolean | — | false | Refuse new connections from a workload on an engine network to private address space (10/8, 172.16/12, 192.168/16, 100.64/10) through the node's uplink. Its own and routed networks are never matched. |
denyPrivileged | boolean | — | false | Refuse `--privileged`. |
mode | string | — | — | `enforce` (refuse) or `warn` (allow and report). Omitted = `enforce`. |
workloadSecurity | WorkloadSecuritySpec | — | — | `baseline`: refuse what `--privileged` is made of — capabilities beyond the Pod Security baseline set, the host's PID/IPC namespace, `seccomp`/`apparmor=unconfined`, host-path bind mounts, host devices and a custom masked/read-only path list that drops the defaults. Omitted = no opinion. |
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.
# ============================================================================
# kind: IPPool (ADR-0046, D3) — um livro de reservas de endereços de HOST
# kind: IPPool (ADR-0046, D3) — a reservation ledger of HOST addresses
#
# PT: Endereços que as rotas reivindicam. Uma rota com `spec.pool: <nome>` segura
# UM endereço do pool enquanto estiver declarada, fica alcançável nesse endereço
# em vez de loopback, e (com `hosts: [host]`) tem os seus nomes apontados para
# ele no /etc/hosts do operador. O livro é observável: `delonix get ippools`,
# `delonix describe ippools <nome>`. Um pool não pode ser removido, nem os seus
# endereços encolher sob uma lease, enquanto uma rota segura um endereço —
# remove ou muda a rota primeiro.
# EN: 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. The ledger is observable: `delonix get ippools`,
# `delonix describe ippools <name>`. A pool cannot be removed, nor its addresses
# shrink under a lease, while a route holds an address — remove or change the
# route first.
#
# PT: Campos spec: addresses (um endereço, uma gama `a-b`, ou um CIDR — IPv4 apenas;
# um CIDR mais largo que /31 deixa de fora rede e broadcast), announce
# (`local` default: o endereço TEM de já estar numa interface deste host, o
# apply confere; `l2` é planeado e recusado), interface (só com `announce: l2`).
# EN: spec fields: addresses (a single address, a range `a-b`, or a CIDR — IPv4 only;
# a CIDR wider than /31 leaves out network and broadcast), announce (`local`
# default: the address MUST already be on an interface of this host, the apply
# checks it; `l2` is planned and refused), interface (only with `announce: l2`).
#
# PT: 127.0.0.10-12 usam-se aqui porque todo o 127/8 está no loopback e não precisa
# de setup. Aplicar: delonix stack apply -f examples/networking/ippool.yaml
# EN: 127.0.0.10-12 are used here because every 127/8 address is on the loopback and
# needs no setup. Apply: delonix stack apply -f examples/networking/ippool.yaml
# ============================================================================
# ── 1. local — uma gama já presente no loopback ──────────────────────────────
# local — a range already present on the loopback
---
apiVersion: networking.delonix.io/v1alpha1
kind: IPPool
metadata: { name: edge }
spec:
addresses: ["127.0.0.10-127.0.0.12"] # PT: um endereço, uma gama `a-b`, ou um CIDR | EN: a single address, a range `a-b`, or a CIDR
announce: local # PT: local (default): o endereço já está numa interface | EN: local (default): the address is already on an interface
Campos de spec — 3spec fields — 3
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
addresses | array<string> | — | [] | Addresses the pool owns: 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 addresses. |
announce | string | — | — | How a claimed address is made reachable. `local` (default): it is already on an interface of this host. `l2` is planned (ADR-0046 phase 4) and refused. |
interface | string | — | — | Interface `announce: l2` would add the address to. Only valid with `l2`. |
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, regras, NAT e políticas num appliance de perímetro
# kind: NetworkGateway — aliases, rules, NAT and policies on a perimeter appliance
#
# PT: `spec.provider` nomeia um provider de gateway registado: `opnsense`,
# registado por DELONIX_OPNSENSE_URL e uma credencial — um par chave/segredo de
# API gerado (DELONIX_OPNSENSE_CREDENTIAL nomeia um `kind: Secret` com `key` e
# `secret`); a password de uma conta é recusada pelo appliance. `stack validate`
# não o contacta. Aliases aplicam-se antes das regras e removem-se depois (o
# appliance confere a origem de uma regra contra a tabela de aliases). Posse por
# marca `delonix-owner:<token>`: um alias/regra do mesmo nome sem a marca é
# recusado, nunca adoptado. Não há update in place — um spec mudado é removido e
# reaplicado.
# EN: `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. `stack validate` does not
# contact it. Aliases are applied before rules and removed after them (the
# appliance checks a rule's source against its alias table). Ownership via a
# `delonix-owner:<token>` mark: an alias/rule with the same name and no mark is
# refused, never adopted. There is no update in place — a changed spec is removed
# and applied again.
#
# PT: Campos spec: provider, aliases, rules, policies, nat. `policies` declara uma
# direcção da política de um alvo na forma de uma NetworkPolicy, baixada para
# regras de filtro do appliance (regra n em `sequence + n - 1`, default logo a
# seguir). `nat` declara SNAT/DNAT (ADR-0051). IPv4 apenas.
# EN: spec fields: provider, aliases, rules, policies, nat. `policies` declares one
# direction of a target's policy in a NetworkPolicy shape, lowered to the
# appliance's filter rules (rule n at `sequence + n - 1`, default right after).
# `nat` declares SNAT/DNAT (ADR-0051). IPv4 only.
#
# PT: Aplicar: delonix stack apply -f examples/networking/networkgateway.yaml
# EN: Apply: delonix stack apply -f examples/networking/networkgateway.yaml
# ============================================================================
# ── 1. edge — aliases, uma regra de filtro, NAT (SNAT+DNAT) e uma política ───
# edge — aliases, a filter rule, NAT (SNAT+DNAT) and a policy
---
apiVersion: networking.delonix.io/v1alpha1
kind: NetworkGateway
metadata: { name: edge }
spec:
# PT: forma-bloco do ADR-0071; o escalar `provider: opnsense` é o mesmo.
# EN: ADR-0071 block form; the scalar `provider: opnsense` is the same thing.
provider: { type: opnsense }
aliases:
- name: delonix_web
kind: host # PT: host | network | EN: host | network
content: ["10.20.0.10", "10.20.0.11"]
description: web tier
rules:
- description: office to web tier # PT: a identidade da regra no appliance | EN: the rule's identity on the appliance
source: 192.168.1.0/24 # PT: um nome de alias, um CIDR, ou any | EN: an alias name, a CIDR, or any
destination: delonix_web
protocol: TCP # PT: omitir para qualquer protocolo | EN: omit for any protocol
nat:
- description: lan source nat # PT: a identidade da regra NAT no appliance | EN: the NAT rule's identity on the appliance
kind: snat # PT: snat (source NAT) | dnat (destination NAT / port-forward) | EN: snat (source NAT) | dnat (destination NAT / port-forward)
interface: wan # PT: o nome do appliance para a interface (lan|wan|opt1) | EN: the appliance's own name for the interface (lan|wan|opt1)
source: 10.77.0.0/24 # PT: snat: a rede traduzida, no seu endereço de rede | EN: snat: the network translated, at its network address
target: interface-address # PT: snat: `interface-address` (default) ou um endereço | EN: snat: `interface-address` (default) or an address
- description: forward https to web
kind: dnat # PT: um port-forward | EN: a port forward
interface: wan
protocol: tcp # PT: dnat: tcp | udp | EN: dnat: tcp | udp
port: 443 # PT: dnat: a porta no endereço da interface | EN: dnat: the port on the interface's address
target: 10.20.0.10 # PT: dnat: o endereço para onde se reencaminha (obrigatório) | EN: dnat: the address forwarded to (required)
targetPort: 8443 # PT: dnat: a porta no destino; default = port | EN: dnat: the port on the target; defaults to `port`
policies:
- name: web-in
target: delonix_web # PT: um nome de alias, um prefixo, ou um endereço | EN: an alias name, a prefix, or an address
direction: ingress # PT: ingress (para o alvo) | egress (dele) | EN: ingress (to the target) | egress (from it)
defaultPolicy: deny # PT: o verdict da última regra; deny quando omitido | EN: the last rule's verdict; deny when omitted
sequence: 30000 # PT: a posição da primeira regra nas regras de filtro do appliance | EN: the first rule's position among the appliance's filter rules
rules:
- { proto: tcp, port: "443", from: 192.168.1.0/24 }
- { proto: tcp, port: "22", from: 10.9.0.0/24, log: true } # PT: log: regista cada match | EN: log: logs every match
- { port: "8000-8080", action: deny, stateful: false } # PT: stateful false: não guarda estado do fluxo | EN: stateful false: keeps no state for the flow
Campos de spec — 5spec fields — 5
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
aliases | array<GatewayAliasSpec> | — | [] | — |
nat | array<GatewayNatSpec> | — | — | Address translation on the appliance (ADR-0059 F5): source NAT for a network leaving through an interface, destination NAT forwarding a port of an interface to a host. Served by the document's provider, which has to be registered for the NAT role too. |
policies | array<GatewayPolicySpec> | — | — | Policies for a target behind the gateway, in the same shape a `NetworkPolicy` has, lowered to the appliance's filter rules through the policy IR (ADR-0059 F3d/F3e). |
provider | string | object | — | — | — |
rules | array<GatewayRuleSpec> | — | [] | — |
ObjectStorage
Armazenamento de objectos compatível com S3 (ADR-0081). Dois backends, consumidos da MESMA forma (endpoint + accessKey/secretKey lidos de um Secret): cephRgw provisiona uma identidade S3 de tenant no Ceph Object Gateway do nó e escreve as chaves num Secret gerado (<nome>-s3); external aponta para um S3 que já corres, com credenciais de um Secret teu.
S3-compatible object storage (ADR-0081). Two backends, consumed the SAME way (endpoint + accessKey/secretKey read from a Secret): cephRgw provisions a tenant S3 identity on the node's Ceph Object Gateway and writes its keys into a generated Secret (<name>-s3); external points at a foreign S3 you already run, with credentials from a Secret you provide.
# ============================================================================
# kind: ObjectStorage — armazenamento de objectos S3 (ADR-0081)
# kind: ObjectStorage — S3-compatible object storage (ADR-0081)
#
# PT: Dois backends. `cephRgw` provisiona uma identidade S3 de tenant no Ceph
# Object Gateway do nó e escreve as chaves num Secret gerado (<nome>-s3).
# `external` aponta para um S3 que já corres, com credenciais de um Secret teu.
# Um workload consome os dois da MESMA forma: endpoint + accessKey/secretKey
# lidos do Secret.
# EN: Two backends. `cephRgw` provisions a tenant S3 identity on the node's Ceph
# Object Gateway and writes its keys into a generated Secret (<name>-s3).
# `external` points at a foreign S3 you already run, with credentials from a
# Secret you provide. A workload consumes either the SAME way: endpoint +
# accessKey/secretKey read from the Secret.
#
# PT: Aplicar: delonix stack apply -f examples/storage/objectstorage.yaml
# EN: Apply: delonix stack apply -f examples/storage/objectstorage.yaml
# ============================================================================
# ── 1. cephRgw — provisionar no Ceph Object Gateway do nó ────────────────────
# cephRgw — provision on the node's Ceph Object Gateway
---
apiVersion: storage.delonix.io/v1alpha1
kind: ObjectStorage
metadata:
name: media
spec:
backend: cephRgw # PT: provisiona no RGW do nó | EN: provision on the node's RGW
bucket: media # PT: o tenant cria-o por S3 com as chaves abaixo | EN: the tenant creates it over S3 with the keys below
cephRgw:
uid: media # PT: id do utilizador S3 (default: metadata.name) | EN: S3 user id (default: metadata.name)
displayName: Media store # PT: rótulo cosmético na identidade S3 | EN: cosmetic label on the S3 identity
endpoint: http://rgw.internal:80 # PT: endpoint S3 público exposto aos consumidores | EN: public S3 endpoint exposed to consumers
quota: # PT: tecto opcional do uso S3 total do tenant | EN: optional ceiling on the tenant's total S3 usage
maxSize: 100G # PT: total de bytes em todos os buckets | EN: total bytes across all buckets
maxObjects: 100000 # PT: total de objectos | EN: total object count
# PT: No apply, as credenciais ficam em secret/media-s3 (accessKey, secretKey, endpoint, bucket).
# EN: On apply, credentials land in secret/media-s3 (accessKey, secretKey, endpoint, bucket).
# ── 2. external — trazer o teu próprio S3 (AWS, MinIO, Backblaze, …) ─────────
# external — bring your own S3 (AWS, MinIO, Backblaze, …)
---
apiVersion: storage.delonix.io/v1alpha1
kind: ObjectStorage
metadata:
name: backups
spec:
backend: external
bucket: company-backups
external:
endpoint: https://s3.eu-west-1.amazonaws.com
region: eu-west-1 # PT: só quando o store precisa | EN: only when the store needs one
credentialsSecret: aws-s3 # PT: Secret existente com accessKey + secretKey | EN: existing Secret with accessKey + secretKey
Campos de spec — 4spec fields — 4
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
backend | string | simyes | — | Which store backs this: `cephRgw` (the engine provisions an S3 identity on the node's Ceph gateway) or `external` (bring your own S3). |
bucket | string | simyes | — | The bucket name. For `cephRgw` the tenant creates it over S3 with the keys handed back; for `external` it names the bucket on the foreign store. |
cephRgw | CephRgwBlock | — | — | Present for (and only for) `backend: cephRgw`. |
external | ExternalBlock | — | — | Present for (and only for) `backend: external`. |
StoragePool
Usar uma storage pool que o administrador JÁ declarou no allowlist do nó (/etc/delonix/storage-pools.yaml): o driver e o que a suporta (btrfs/zfs/lvm-thin/directório/ceph) vêm de LÁ e só de lá. Este Kind diz apenas «este nó usa a pool X, com este tecto de sobre-alocação» — device/path/comando são recusados pelo NOME. O motor nunca cria nem destrói uma pool; tirar o Kind só deixa de a usar. Alocam-se volumes nela com um kind: Volume que traz pool: <nome>.
Use a storage pool the administrator has ALREADY declared in the node's allowlist (/etc/delonix/storage-pools.yaml): its driver and backing (btrfs/zfs/lvm-thin/directory/ceph) come from THERE and there only. This Kind only says «this node uses pool X, with this over-allocation ceiling» — device/path/command are refused by NAME. The engine never creates or destroys a pool; dropping the Kind just stops using it. Allocate volumes on it with a kind: Volume carrying pool: <name>.
# ============================================================================
# kind: StoragePool — usar uma storage pool que o administrador declarou (ADR-0067)
# kind: StoragePool — use a storage pool the administrator declared (ADR-0067)
#
# PT: Uma pool EXISTE porque o administrador a escreveu no allowlist do nó
# (/etc/delonix/storage-pools.yaml): o driver e o que a suporta (btrfs, zfs,
# lvm-thin, um directório, ceph) vêm de LÁ e só de lá. Este Kind diz apenas
# «o motor deste nó usa a pool chamada X, com este tecto de sobre-alocação» —
# nunca carrega device, path nem comando, e esses campos são recusados pelo
# NOME (não ignorados). O motor NUNCA cria nem destrói uma pool; apagar o Kind
# só deixa de a usar — a pool e o que está nela ficam. `metadata.name` TEM de
# ser uma pool do allowlist do nó.
# EN: A pool EXISTS because the administrator wrote it into the node's allowlist
# (/etc/delonix/storage-pools.yaml): its driver and what backs it (btrfs, zfs,
# lvm-thin, a directory, ceph) come from THERE and there only. This Kind only
# says «this node's engine uses the pool called X, with this over-allocation
# ceiling» — it never carries a device, path or command, and those fields are
# refused by NAME (not ignored). The engine NEVER creates or destroys a pool;
# deleting the Kind just stops using it — the pool and whatever is in it stay.
# `metadata.name` MUST be a pool from the node's allowlist.
#
# PT: Depois de aplicada, aloca volumes nela com um `kind: Volume` que traz
# `pool: <nome>` + `size:` (ver volume.yaml, cenário 8), ou
# `delonix volume create <v> --pool <nome> --size 10G`. Lista o que o nó
# declarou com `delonix get storagepools`.
# EN: Once applied, allocate volumes in it with a `kind: Volume` carrying
# `pool: <name>` + `size:` (see volume.yaml, scenario 8), or
# `delonix volume create <v> --pool <name> --size 10G`. List what the node
# declared with `delonix get storagepools`.
#
# PT: Aplicar: delonix stack apply -f examples/storage/storagepool.yaml
# EN: Apply: delonix stack apply -f examples/storage/storagepool.yaml
# ============================================================================
# ── 1. mínimo — só o nome, usar os defaults ─────────────────────────────────
# minimal — just the name, use the defaults
# PT: Sem `spec`: sem sobre-alocação (maxRatio 1.0, a soma dos tamanhos nunca
# excede a capacidade) e alerta aos 80% de uso.
# EN: No `spec`: no over-allocation (maxRatio 1.0, allocated sizes never exceed
# capacity) and an alert at 80% usage.
---
apiVersion: storage.delonix.io/v1alpha1
kind: StoragePool
metadata:
name: fast # PT: tem de existir no allowlist do nó | EN: must exist in the node's allowlist
spec: {}
# ── 2. com sobre-alocação e limiar de alerta próprio ────────────────────────
# with over-allocation and a custom alert threshold
# PT: `overcommit.maxRatio` é a fracção da capacidade que a soma dos tamanhos
# alocados pode exceder (entre 0.01 e 100; 1.0 não permite sobre-alocação).
# `alertPct` é a % de uso da pool acima da qual uma alocação avisa (1..=99,
# default 80). Uma pool com ≥95% de uso recusa QUALQUER volume novo.
# EN: `overcommit.maxRatio` is how far the sum of allocated sizes may exceed the
# pool's capacity (between 0.01 and 100; 1.0 allows no over-allocation).
# `alertPct` is the pool-usage % above which an allocation warns (1..=99,
# default 80). A pool at ≥95% usage refuses ANY new volume.
---
apiVersion: storage.delonix.io/v1alpha1
kind: StoragePool
metadata:
name: bulk
spec:
overcommit:
maxRatio: 1.5 # PT: alocar até 150% da capacidade (thin/thin-provision) | EN: allocate up to 150% of capacity (thin provisioning)
alertPct: 70 # PT: avisa a partir de 70% de uso | EN: warn from 70% usage
Campos de spec — 2spec fields — 2
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
alertPct | integer | — | — | Percentage of the pool in use above which an allocation warns (default 80). |
overcommit | Overcommit | — | — | How far the sizes allocated in the pool may exceed its capacity. |
GitOpsSource
Um repositório git de onde o nó reconcilia (ADR-0021). Aplicar escreve um registo e, com sync.automated: true, um timer systemd; cada disparo corre UMA passagem e termina: fetch do ref → resolve a commit → verifica a assinatura → lê o YAML sob path → guarda → planeia → aplica. Um commit que não carrega, falha a guarda, ou planeia uma acção bloqueada por safety deixa o stack a correr intacto. reconcile corre uma passagem à mão; suspend/resume param e retomam as passagens.
A git repository this node reconciles from (ADR-0021). Applying writes a record and, with sync.automated: true, a systemd timer; each fire runs ONE pass and exits: fetch the ref → resolve to a commit → verify the signature → read the YAML under path → guard → plan → apply. A commit that fails to load, fails the guard, or plans a safety-blocked action leaves the running stack untouched. reconcile runs a pass by hand; suspend/resume stop and restart the passes.
# ============================================================================
# kind: GitOpsSource — um repositório git de onde o nó reconcilia (ADR-0021)
# kind: GitOpsSource — a git repository this node reconciles from (ADR-0021)
#
# PT: Aplicar escreve um registo e, com `sync.automated: true`, um timer systemd
# (`delonix-gitops-<namespace>.<name>.timer`). Cada disparo corre UMA passagem
# e termina — nada fica residente:
# fetch do ref → resolve a commit → verifica a assinatura (quando pedido)
# → lê o YAML sob `path` da árvore do commit → guarda → planeia → aplica
# Um commit que não carrega, falha a guarda/o grafo, ou planeia uma acção que
# `safety` bloqueia deixa o stack a correr intacto; `delonix gitops status`
# nomeia-o. `reconcile <name>` corre uma passagem à mão; `suspend`/`resume`
# param e retomam as passagens sem um commit.
# EN: Applying writes a record and, with `sync.automated: true`, a systemd timer
# (`delonix-gitops-<namespace>.<name>.timer`). Each fire runs ONE pass and
# exits — nothing stays resident:
# fetch the ref → resolve to a commit → verify the signature (when asked)
# → read the YAML under `path` from the commit's tree → guard → plan → apply
# A commit that does not load, fails the guard/graph, or plans an action
# `safety` blocks leaves the running stack untouched; `delonix gitops status`
# names it. `reconcile <name>` runs a pass by hand; `suspend`/`resume` stop
# and restart the passes without a commit.
#
# PT: DOIS stacks, nunca um: o stack que aplica ESTE ficheiro (o bootstrap:
# `delonix stack apply -f … --name bootstrap`) declara só a fonte; as
# passagens aplicam o repositório em `spec.stack` (default
# `gitops.<namespace>.<name>`). Um apply/plan/prune/validate à mão do stack
# das passagens é recusado (DX-1601).
# EN: TWO stacks, never one: the stack that applies THIS file (the bootstrap:
# `delonix stack apply -f … --name bootstrap`) declares only the source; the
# passes apply the repository into `spec.stack` (default
# `gitops.<namespace>.<name>`). A hand-run apply/plan/prune/validate of the
# passes' own stack is refused (DX-1601).
#
# PT: As credenciais são NOMEADAS, nunca embutidas: cada `*Ref` é um kind: Secret
# que já existe no nó antes da fonte ser aplicada.
# secretRef → chave `identity` (a chave SSH privada)
# knownHostsRef → chave `known_hosts` (os host keys fixados)
# trustedKeysRef → chave `allowed_signers` (um ficheiro SSH allowed_signers)
# EN: Credentials are NAMED, never carried: each `*Ref` is a kind: Secret that
# already exists on the node before the source is applied.
# secretRef → key `identity` (the SSH private key)
# knownHostsRef → key `known_hosts` (the pinned host keys)
# trustedKeysRef → key `allowed_signers` (an SSH allowed_signers file)
#
# PT: Aplicar: delonix stack apply -f examples/gitops/gitopssource.yaml --name bootstrap
# EN: Apply: delonix stack apply -f examples/gitops/gitopssource.yaml --name bootstrap
# ============================================================================
# ── 1. ssh + commits assinados — a fonte de produção, tudo declarado ────────
# ssh + signed commits — the production source, everything declared
---
apiVersion: gitops.delonix.io/v1alpha1
kind: GitOpsSource
metadata:
name: production
namespace: production
spec:
source:
url: ssh://git@git.example.org/infra/web.git # PT: também file:///<path> e https:// (anónimo) | EN: also file:///<path> and https:// (anonymous)
ref: refs/heads/main # PT: um branch ou tag, resolvido a commit primeiro | EN: a branch or a tag, resolved to a commit first
path: environments/production # PT: paths escondidos (.github/…) nunca são lidos | EN: hidden paths (.github/…) are never read
interval: 60s # PT: de 15s a 24h | EN: 15s to 24h
secretRef: web-deploy-key # PT: Secret com a chave SSH privada, sob `identity` (OBRIGATÓRIO em ssh://) | EN: Secret with the SSH private key, under `identity` (REQUIRED for ssh://)
knownHostsRef: git-known-hosts # PT: Secret com os host keys fixados, sob `known_hosts` (OBRIGATÓRIO em ssh://) | EN: Secret with the pinned host keys, under `known_hosts` (REQUIRED for ssh://)
signedCommitsRequired: true # PT: recusa um commit cuja assinatura SSH não seja de trustedKeysRef | EN: refuse a commit whose SSH signature is not from trustedKeysRef
trustedKeysRef: web-signers # PT: Secret com um allowed_signers SSH, sob `allowed_signers` (anda a par de signedCommitsRequired) | EN: Secret with an SSH allowed_signers, under `allowed_signers` (goes together with signedCommitsRequired)
sync:
automated: true # PT: instala o timer; false = regista e só corre com `gitops reconcile` | EN: installs the timer; false = registers and runs only on `gitops reconcile`
safety:
allow: [Create, Update] # PT: as acções que uma passagem pode tomar; Adopt/Replace/Reboot são opt-in; Delete é prune; Conflict nunca | EN: the actions a pass may take; Adopt/Replace/Reboot are opt-in; Delete is prune; Conflict never
prune: false # PT: Delete só corre com prune: true | EN: Delete runs only with prune: true
maximumDeletes: 5 # PT: o máximo que uma passagem pode podar | EN: the most a single pass may prune
protectedResources:
- Volume/* # PT: <Kind>/<nome> ou <Kind>/* que uma passagem nunca poda | EN: <Kind>/<name> or <Kind>/* a pass never prunes
kinds: [] # PT: vazio = o conjunto default; nomeia VirtualMachine, SystemContainer, NetworkZone, NetworkGateway, StoragePool, Gateway ou IPPool para deixar um entrar | EN: empty = the default set; name VirtualMachine, SystemContainer, NetworkZone, NetworkGateway, StoragePool, Gateway or IPPool to let one in
stack: production-web # PT: o stack onde uma passagem aplica (default gitops.<ns>.<name>); nunca o stack que aplica este ficheiro | EN: the stack a pass applies into (default gitops.<ns>.<name>); never the stack that applies this file
# ── 2. https anónimo — leitura pública, sem credenciais nem assinatura ──────
# anonymous https — public read, no credentials and no signature
---
apiVersion: gitops.delonix.io/v1alpha1
kind: GitOpsSource
metadata:
name: public-addons
namespace: default
spec:
source:
url: https://github.com/example/addons.git # PT: https:// é anónimo — sem credenciais no URL (um Secret https:// não está implementado) | EN: https:// is anonymous — no credentials in the URL (an https:// credential is not implemented)
ref: refs/tags/v1.4.0 # PT: uma tag fixa; refs/heads/<branch> para seguir um branch | EN: a pinned tag; refs/heads/<branch> to follow a branch
path: manifests # PT: directório dentro do repositório; vazio = a raiz | EN: directory inside the repository; empty = the root
interval: 5m # PT: de 15s a 24h | EN: 15s to 24h
# PT: secretRef/knownHostsRef/trustedKeysRef são SÓ para ssh:// — recusados aqui.
# EN: secretRef/knownHostsRef/trustedKeysRef are ssh:// ONLY — refused here.
signedCommitsRequired: false # PT: anda a par de trustedKeysRef: ambos ausentes aqui | EN: goes together with trustedKeysRef: both absent here
sync:
automated: false # PT: sem timer — corres `delonix gitops reconcile public-addons` do teu próprio escalonador | EN: no timer — you run `delonix gitops reconcile public-addons` from your own scheduler
safety:
allow: [Create, Update]
prune: false
maximumDeletes: 5
kinds: []
stack: cluster-addons # PT: um stack próprio, distinto do bootstrap | EN: a stack of its own, distinct from the bootstrap
Campos de spec — 4spec fields — 4
| CampoField | TipoType | Obrig.Req. | DefaultDefault | DescriçãoDescription |
|---|---|---|---|---|
safety | SafetySpec | — | {"allow": ["Create", "Update"], "kinds": [], "maximumDeletes": 5, "protectedResources": [], "prune": false} | What a pass may do to the machine. |
source | SourceSpec | simyes | — | Where the manifests come from. |
stack | string | — | — | The stack a pass applies into. Defaults to `gitops.<namespace>.<name>`, a name no `spec.stack` and no bootstrap `--name` spells by accident. The stack that applies the source itself must be another one: a local plan, apply or prune of the pass's stack would see only the source and plan everything the passes applied for deletion (refused, DX-1601). |
sync | SyncSpec | — | {"automated": false} | Whether a timer reconciles it. |
SystemContainer
Um userland inteiro como UMA unidade num nó provider (ADR-0058) — NÃO é um kind: Container: sem exec, logs nem exit status. A parte genérica (image/entrypoint/env/memory/cores/rootfs) é a intenção; o que só o nó Proxmox entende — a folga de swap e a bridge/VLAN do nó — vive inline em provider.spec (ADR-0071). O motor puxa a imagem, nunca o provider.
A whole userland run as one unit on a provider node (ADR-0058) — NOT a kind: Container: no exec, logs or exit status. The generic part (image/entrypoint/env/memory/cores/rootfs) is the intent; what only the Proxmox node understands — the swap allowance and the node bridge/VLAN — lives inline in provider.spec (ADR-0071). The engine pulls the image, never the provider.
# ============================================================================
# kind: SystemContainer — um userland inteiro como uma unidade num nó provider (ADR-0058)
# kind: SystemContainer — a whole userland run as one unit on a provider node (ADR-0058)
#
# PT: Não é um `kind: Container`: não há exec, logs nem exit status. A parte
# genérica (image, entrypoint, env, memory, cores, rootfs) é a intenção; o
# que só o nó Proxmox entende — a folga de swap e a bridge/VLAN do nó — vive
# inline em `provider.spec` (ADR-0071). O motor puxa a imagem, nunca o
# provider. A grafia antiga (`swap`/`network` no topo de `spec`) ainda carrega
# e significa o mesmo — mas não repitas o mesmo campo nos dois sítios.
# EN: Not a `kind: Container`: no exec, logs or exit status. The generic part
# (image, entrypoint, env, memory, cores, rootfs) is the intent; what only
# the Proxmox node understands — the swap allowance and the node bridge/VLAN
# — lives inline in `provider.spec` (ADR-0071). The engine pulls the image,
# never the provider. The old spelling (`swap`/`network` at the top of
# `spec`) still loads and means the same — just don't write a field twice.
#
# PT: Aplicar: delonix stack apply -f examples/compute/systemcontainer.yaml
# EN: Apply: delonix stack apply -f examples/compute/systemcontainer.yaml
# ============================================================================
# ── 1. bloco provider — o Proxmox, com swap + bridge/DHCP inline ─────────────
# provider block — Proxmox, with swap + bridge/DHCP inline
---
apiVersion: compute.delonix.io/v1alpha1
kind: SystemContainer
metadata: { name: edge-dns }
spec:
image: alpine:3.20 # PT: o motor puxa-a (nunca o provider) | EN: the engine pulls it (never the provider)
memory: 256M # PT: RAM dada ao userland | EN: RAM given to the userland
cores: 1 # PT: núcleos de CPU (cpuset) | EN: CPU cores (cpuset)
rootfs: 2 # PT: tamanho do rootfs em GiB | EN: rootfs size in GiB
provider:
type: proxmox # PT: único provider deste porte | EN: the only provider of this port
spec:
swap: 128M # PT: folga de swap do contentor | EN: the container's swap allowance
network: { bridge: vmbr0, dhcp: true } # PT: interface numa bridge do nó, IPv4 por DHCP | EN: interface on a node bridge, IPv4 via DHCP
# ── 2. grafia antiga (swap/network no topo) + entrypoint/env e VLAN ──────────
# old spelling (swap/network at the top) + entrypoint/env and a VLAN
---
apiVersion: compute.delonix.io/v1alpha1
kind: SystemContainer
metadata: { name: edge-proxy }
spec:
image: debian:12
entrypoint: ["/usr/sbin/nginx", "-g", "daemon off;"] # PT: init do contentor (vazio => o da imagem) | EN: the container's init (empty => the image's)
env: # PT: ambiente (não-vazio substitui o da imagem por inteiro) | EN: environment (non-empty replaces the image's whole)
TZ: Africa/Luanda
LOG_LEVEL: info
memory: 512M
cores: 2
rootfs: 4
swap: 256M # PT: grafia antiga — igual a provider.spec.swap | EN: old spelling — same as provider.spec.swap
network: # PT: grafia antiga — igual a provider.spec.network | EN: old spelling — same as provider.spec.network
bridge: vmbr1
vlan: 20 # PT: tag VLAN opcional da interface | EN: optional VLAN tag of the interface
dhcp: false # PT: interface up sem endereço | EN: interface up with no address