Laboratórios
Sessões curtas, do zero a um resultado que se verifica. Cada uma inclui a limpeza — nenhuma deixa nada para trás.
Short sessions, from zero to a verifiable result. Each one includes cleanup — none of them leaves anything behind.
A verificação no fim de cada lab não é decorativa: é o que separa «copiei comandos» de «sei o que isto faz». Onde um passo pode falhar em silêncio, o lab diz exactamente o que olhar.
The verification at the end of each lab isn't decorative: it's what tells apart "I copied commands" from "I know what this does." Where a step could fail silently, the lab tells you exactly what to look at.
Primeiro serviço, em 60 segundosFirst service, in 60 seconds iniciantebeginner
Objectivo: perceber o ciclo run → ps → logs → exec → rm e ver,
com rigor, o que «daemonless» quer dizer neste motor.
# 1. Um serviço web, em segundo plano, publicado na porta 8080 do host
delonix container run -d --name web -p 8080:80 nginx:alpine
# 2. Confirmar — o STATUS diz há quanto tempo está de pé
delonix container ps
# 3. O que «daemonless» É: não há um daemon central arrancado no boot.
# Mas cada recurso tem holders leves — repara em QUEM os pariu (coluna PPID):
ps -o pid,ppid,comm,args -C delonix
# 4. Falar com ele
curl -s localhost:8080 | head -3
# 5. Entrar lá dentro
delonix container exec -it web sh -c 'hostname; id; ls /etc/nginx'
# 6. Ver o que ele escreveu
delonix container logs web | tail -5
# 7. Limpar
delonix container rm -f web
Verificação: o passo 3 mostra alguns processos
delonix — tipicamente netns pin, netns control
e o holder do próprio run — e o PPID de todos é o
systemd --user do teu utilizador, não um serviço do sistema. É esta
a diferença de fundo para o Docker: não há um dockerd central
a supervisionar tudo; há um holder mínimo por recurso, que morre com o recurso.
(Num host tranquilo e sem -p podes nem ver holder nenhum.)
Arnês reproduzível (§7): este lab corre sozinho,
com asserção de resultado funcional (HTTP 200 — não um exit-code isolado),
em docs/labs/lab-1/: prepare → run → verify → cleanup
(bash docs/labs/lab-1/lab.sh). Baseline histórica — validado em delonix 5.1.1 (Linux, cgroup v2, rootless) a 2026-10-24; não re-medido na linha 0.1.0 actual.
Goal: understand the run → ps → logs → exec → rm cycle and see,
precisely, what «daemonless» means in this engine.
# 1. A web service, in the background, published on host port 8080
delonix container run -d --name web -p 8080:80 nginx:alpine
# 2. Confirm — STATUS shows how long it's been up
delonix container ps
# 3. What «daemonless» IS: no central daemon is started at boot.
# But each resource has lightweight holders — note WHO spawned them (PPID):
ps -o pid,ppid,comm,args -C delonix
# 4. Talk to it
curl -s localhost:8080 | head -3
# 5. Go inside
delonix container exec -it web sh -c 'hostname; id; ls /etc/nginx'
# 6. See what it wrote
delonix container logs web | tail -5
# 7. Clean up
delonix container rm -f web
Verification: step 3 shows a few
delonix processes — typically netns pin,
netns control and the run holder itself — and every
one's PPID is your own systemd --user, not a system service. That is
the fundamental difference from Docker: there is no central
dockerd supervising everything; there's a minimal holder per
resource, which dies with the resource. (On a quiet host with no -p
you may see no holder at all.)
Reproducible harness (§7): this lab runs on its
own, asserting a functional result (HTTP 200 — not an isolated exit
code), under docs/labs/lab-1/: prepare → run → verify →
cleanup (bash docs/labs/lab-1/lab.sh). Historical baseline — validated on delonix 5.1.1 (Linux, cgroup v2, rootless) on 2026-10-24; not re-measured on the current 0.1.0 line.
Materiais: guia escrito · arnês executável (prepare → run → verify → cleanup).
Materials: written guide · runnable harness (prepare → run → verify → cleanup).
Limites de recursos que pegam mesmoResource limits that actually take iniciantebeginner
Objectivo: descobrir se este host aplica limites — e o que fazer quando não aplica. É o erro nº1 de quem começa em rootless.
# 1. Perguntar ANTES de assumir
delonix system setup
# 2. Se disser INERT ou PARTIAL, a 1.ª correcção não precisa de root nenhum
systemd-run --user --scope -p Delegate=yes -- delonix system setup
# … e é dentro desse scope que corres os passos seguintes.
# Só se ELE ainda disser que falta o `cpu`:
# sudo delonix system setup --delegate (+ logout / login)
# 3. Um container com tecto de memória
delonix container run -d --name limitado -m 128M alpine sleep 300
# 4. A prova: ler o cgroup REAL, não o que o comando diz
PID=$(delonix container inspect limitado | jq -r .pid)
cat /sys/fs/cgroup$(awk -F: '/^0::/{print $3}' /proc/$PID/cgroup)/memory.max
delonix container rm -f limitado
Verificação: o passo 4 imprime
134217728 (128 MiB), não max. Se imprimir
max, a delegação não está feita — e o container corre sem
limite nenhum, em silêncio. É por isso que o passo 1 existe.
Nota: cpuset e io
aparecem quase sempre como absent, e está certo — num Ubuntu de
fábrica o user.slice (da root) só passa cpu memory
pids para baixo. Nada aqui precisa deles.
Arnês reproduzível (§7): este lab corre sozinho,
com asserção de resultado funcional — o memory.max REAL do
cgroup do kernel, não a saída do comando — e falha em voz alta
se o limite não pegou (memory.max=max), em vez de passar em silêncio,
em docs/labs/lab-2/: prepare → run → verify → cleanup
(bash docs/labs/lab-2/lab.sh). Baseline histórica — validado em delonix 5.1.1 (Linux, cgroup v2, rootless) a 2026-10-24; não re-medido na linha 0.1.0 actual.
Goal: find out whether this host enforces limits — and what to do when it doesn't. It's mistake #1 for anyone starting out in rootless.
# 1. Ask BEFORE assuming
delonix system setup
# 2. If it says INERT or PARTIAL, the 1st fix needs no root at all
systemd-run --user --scope -p Delegate=yes -- delonix system setup
# … and you run the following steps INSIDE that scope.
# Only if it STILL says `cpu` is missing:
# sudo delonix system setup --delegate (+ logout / login)
# 3. A container with a memory ceiling
delonix container run -d --name limited -m 128M alpine sleep 300
# 4. The proof: read the REAL cgroup, not what the command says
PID=$(delonix container inspect limited | jq -r .pid)
cat /sys/fs/cgroup$(awk -F: '/^0::/{print $3}' /proc/$PID/cgroup)/memory.max
delonix container rm -f limited
Verification: step 4 prints
134217728 (128 MiB), not max. If it prints
max, delegation isn't set up — and the container runs with
no limit at all, silently. That's why step 1 exists.
Note: cpuset and io
almost always show up as absent, and that's correct — on a stock
Ubuntu, root's user.slice only passes cpu memory
pids down. Nothing here needs them.
Reproducible harness (§7): this lab runs on its
own, asserting a functional result — the REAL memory.max of
the kernel cgroup, not the command's output — and it fails loudly
if the limit didn't take (memory.max=max) instead of passing
silently, under docs/labs/lab-2/: prepare → run → verify →
cleanup (bash docs/labs/lab-2/lab.sh). Historical baseline — validated on delonix 5.1.1 (Linux, cgroup v2, rootless) on 2026-10-24; not re-measured on the current 0.1.0 line.
Materiais: guia escrito · arnês executável (prepare → run → verify → cleanup).
Materials: written guide · runnable harness (prepare → run → verify → cleanup).
Duas aplicações que se falam por nomeTwo apps that talk to each other by name intermédiointermediate
Objectivo: rede de utilizador, DNS interno, e isolamento — sem configurar DNS nenhum.
# 1. Uma rede própria
delonix network create loja
# 2. Base de dados e app, ambas nela
delonix container run -d --name db --net loja -e POSTGRES_PASSWORD=x postgres:16-alpine
delonix container run -d --name app --net loja -p 8080:80 nginx:alpine
# 3. A app resolve a db PELO NOME, sem /etc/hosts nem variáveis
delonix container exec app sh -c 'getent hosts db; nc -z db 5432 && echo "porta aberta"'
# 4. Fechar a db a tudo menos à app (alcançabilidade DIRIGIDA)
cat > dep.yaml <<'EOF'
apiVersion: delonix.io/v1
kind: Dependency
metadata:
name: app-conhece-db
spec:
from: app
to: db
ports: ["5432"]
EOF
delonix stack apply -f dep.yaml
# 5. Provar: um terceiro container na MESMA rede já não alcança a db
delonix container run --rm --net loja alpine sh -c 'nc -z -w2 db 5432 || echo BLOQUEADO'
delonix container rm -f db app; delonix network rm loja; rm dep.yaml
Verificação: o passo 3 diz "porta aberta" e o
passo 5 diz "BLOQUEADO". A mesma rede, dois resultados — é isso que
kind: Dependency faz e uma rede sozinha não faz.
Arnês reproduzível (§7): este lab corre sozinho,
com asserção de resultado funcional — a app resolve a db pelo
nome (getent hosts db → IP) e a porta responde ao par autorizado — e
falha em voz alta se a db ficar aberta a todos
(o terceiro container ainda a alcançar), distinguindo «bloqueado por política» de
«serviço em baixo», em vez de passar em silêncio, em docs/labs/lab-3/:
prepare → run → verify → cleanup (bash docs/labs/lab-3/lab.sh).
Baseline histórica — validado em delonix 5.1.1 (Linux, cgroup v2, rootless) a 2026-10-24; não re-medido na linha 0.1.0 actual.
Goal: user network, internal DNS, and isolation — with zero DNS configuration.
# 1. Your own network
delonix network create shop
# 2. Database and app, both on it
delonix container run -d --name db --net shop -e POSTGRES_PASSWORD=x postgres:16-alpine
delonix container run -d --name app --net shop -p 8080:80 nginx:alpine
# 3. The app resolves db BY NAME, no /etc/hosts, no env vars
delonix container exec app sh -c 'getent hosts db; nc -z db 5432 && echo "port open"'
# 4. Lock db down to everyone except app (DIRECTED reachability)
cat > dep.yaml <<'EOF'
apiVersion: delonix.io/v1
kind: Dependency
metadata:
name: app-knows-db
spec:
from: app
to: db
ports: ["5432"]
EOF
delonix stack apply -f dep.yaml
# 5. Prove it: a third container on the SAME network can no longer reach db
delonix container run --rm --net shop alpine sh -c 'nc -z -w2 db 5432 || echo BLOCKED'
delonix container rm -f db app; delonix network rm shop; rm dep.yaml
Verification: step 3 says "port open" and
step 5 says "BLOCKED". Same network, two different outcomes — that's what
kind: Dependency does that a plain network alone doesn't.
Reproducible harness (§7): this lab runs on its
own, asserting a functional result — the app resolves db by
name (getent hosts db → IP) and the port answers the authorized peer —
and it fails loudly if db is left open to everyone
(the third container still reaching it), telling "blocked by policy" apart from
"service down" instead of passing silently, under docs/labs/lab-3/:
prepare → run → verify → cleanup (bash docs/labs/lab-3/lab.sh).
Historical baseline — validated on delonix 5.1.1 (Linux, cgroup v2, rootless) on 2026-10-24; not re-measured on the current 0.1.0 line.
Materiais: guia escrito · arnês executável (prepare → run → verify → cleanup).
Materials: written guide · runnable harness (prepare → run → verify → cleanup).
Do Dockerfile à imagem, sem daemonFrom Dockerfile to image, no daemon intermédiointermediate
Objectivo: construir uma imagem e correr o que construíste.
mkdir -p lab-build && cd lab-build
cat > Delonixfile <<'EOF'
FROM alpine:latest
RUN apk add --no-cache curl
COPY ola.txt /ola.txt
HEALTHCHECK CMD test -f /ola.txt
CMD ["sh", "-c", "cat /ola.txt; sleep 300"]
EOF
echo "construído com delonix" > ola.txt
# `Delonixfile` é procurado antes de `Dockerfile` — sem -f nenhum
delonix build -t minha-app:1.0 .
# `--wait` bloqueia até o HEALTHCHECK passar: sem isto escreve-se
# `until ...; do sleep 1; done`, e escreve-se mal
delonix container run -d --name a1 --wait --health-interval 2 minha-app:1.0
delonix container ps
delonix container rm -f a1; delonix image remove minha-app:1.0; cd ..; rm -rf lab-build
Verificação: o ps mostra
(healthy) na coluna STATUS. O motor está a sondar o container
sozinho — sem systemd, ao contrário do Podman rootless.
Arnês reproduzível (§7): este lab corre sozinho,
com asserção de resultado funcional — lê (healthy) da coluna
STATUS de container ps -o json (não só Up), com retry, e
prova que delonix build acha o Delonixfile antes de
qualquer Dockerfile sem -f — e falha em voz
alta distinguindo «build falhou» de «healthcheck nunca passou», em vez de
passar em silêncio, em docs/labs/lab-4/:
prepare → run → verify → cleanup (bash docs/labs/lab-4/lab.sh).
Baseline histórica — validado em delonix 5.1.1 (Linux, cgroup v2, rootless) a 2026-10-24; não re-medido na linha 0.1.0 actual.
Goal: build an image and run what you built.
mkdir -p lab-build && cd lab-build
cat > Delonixfile <<'EOF'
FROM alpine:latest
RUN apk add --no-cache curl
COPY hello.txt /hello.txt
HEALTHCHECK CMD test -f /hello.txt
CMD ["sh", "-c", "cat /hello.txt; sleep 300"]
EOF
echo "built with delonix" > hello.txt
# `Delonixfile` is looked for before `Dockerfile` — no -f needed
delonix build -t my-app:1.0 .
# `--wait` blocks until HEALTHCHECK passes: without it you'd have to
# write `until ...; do sleep 1; done` yourself, and get it wrong
delonix container run -d --name a1 --wait --health-interval 2 my-app:1.0
delonix container ps
delonix container rm -f a1; delonix image remove my-app:1.0; cd ..; rm -rf lab-build
Verification: ps shows
(healthy) in the STATUS column. The engine is probing the
container on its own — no systemd, unlike rootless Podman.
Reproducible harness (§7): this lab runs on its
own, asserting a functional result — it reads (healthy) from
the STATUS column of container ps -o json (not just Up),
with retry, and proves delonix build finds the Delonixfile
before any Dockerfile with no -f — and it fails
loudly, telling "build failed" apart from "healthcheck never passed"
instead of passing silently, under docs/labs/lab-4/:
prepare → run → verify → cleanup (bash docs/labs/lab-4/lab.sh).
Historical baseline — validated on delonix 5.1.1 (Linux, cgroup v2, rootless) on 2026-10-24; not re-measured on the current 0.1.0 line.
Materiais: guia escrito · arnês executável (prepare → run → verify → cleanup).
Materials: written guide · runnable harness (prepare → run → verify → cleanup).
Uma microVM a sérioA real microVM intermédiointermediate
Objectivo: uma VM completa com o mesmo CLI dos containers.
# 1. Sem imagem local, o create descarrega a oficial sozinho
delonix vm create dev
# 2. Onde ficou, e com que IP
delonix vm ls
delonix describe vms dev
# 3. Entrar (voltar ao host: Ctrl+])
delonix vm console dev
# 4. Checkpoint de sistema — memória E disco, com a VM a correr
delonix vm snapshot create dev limpa
delonix vm snapshot ls dev
# 5. Estragar alguma coisa lá dentro e voltar atrás
delonix vm snapshot restore dev limpa
# Teardown: vm destroy leva o disco, os snapshots E o record (sem órfãos)
delonix vm destroy dev
Verificação: o passo 4 devolve sem erro com a
VM A CORRER, e o snapshot fica como um checkpoint de sistema —
memória E disco. Com a VM parada seria disk-only (não
falha — o vm stop faz undefine do domínio em execução, mas
preserva disco, record e snapshots). O teardown é vm destroy, que
remove tudo o que a VM possui.
Arnês reproduzível (§7): este lab corre sozinho,
com asserção de resultado funcional — lê de vm ls -o json
que a VM fica Running com IPv4, que o snapshot tirado com a VM a
correr aparece em snapshot ls e que o restore a mantém
de pé, e prova que vm destroy não deixa órfãos
(describe vm deixa de resolver) — falha em voz alta
distinguindo «VM nunca arrancou» de «sem IP» de «snapshot/restore falhou», em
docs/labs/lab-5/: prepare → run → verify → cleanup
(bash docs/labs/lab-5/lab.sh). Baseline histórica — validado em delonix 5.1.1 (Linux, libvirt, rootless) a 2026-10-24; não re-medido na linha 0.1.0 actual.
Goal: a full VM with the same CLI as containers.
# 1. With no local image, create downloads the official one on its own
delonix vm create dev
# 2. Where it landed, and with what IP
delonix vm ls
delonix describe vms dev
# 3. Go in (back to the host: Ctrl+])
delonix vm console dev
# 4. System checkpoint — memory AND disk, VM still running
delonix vm snapshot create dev clean
delonix vm snapshot ls dev
# 5. Break something inside and roll back
delonix vm snapshot restore dev clean
# Teardown: vm destroy takes the disk, the snapshots AND the record (no orphans)
delonix vm destroy dev
Verification: step 4 returns with no error and
the VM STILL RUNNING, and the snapshot is a system checkpoint —
memory AND disk. A stopped VM would give a disk-only
snapshot (it does not fail — vm stop undefines the running
domain but preserves the disk, record and snapshots). The teardown is
vm destroy, which removes everything the VM owns.
Reproducible harness (§7): this lab runs on its
own, asserting a functional result — it reads from vm ls -o json
that the VM reaches Running with an IPv4, that the snapshot taken
while running shows up in snapshot ls and that restore
keeps it up, and it proves vm destroy leaves no orphans
(describe vm stops resolving) — it fails loudly,
telling "VM never started" apart from "no IP" apart from "snapshot/restore
failed", under docs/labs/lab-5/: prepare → run → verify → cleanup
(bash docs/labs/lab-5/lab.sh). Historical baseline — validated on delonix 5.1.1 (Linux, libvirt, rootless) on 2026-10-24; not re-measured on the current 0.1.0 line.
Materiais: guia escrito · arnês executável (prepare → run → verify → cleanup).
Materials: written guide · runnable harness (prepare → run → verify → cleanup).
A tua própria imagem de VM, com VMfileYour own VM image, with a VMfile avançadoadvanced
Objectivo: construir uma imagem qcow2 à tua medida, como se constrói uma imagem de container.
mkdir -p lab-vm && cd lab-vm
# Scaffold que CONSTRÓI COMO ESTÁ — apaga o que não precisares
delonix vm image init --name minha-base
cat VMfile
# Precisa de libguestfs no host: sudo apt install libguestfs-tools
delonix vm image build -t minha-base:1.0 .
delonix vm ls
# Um RUN com `apt-get install` precisa de rede no convidado, e pede-se:
# delonix vm image build --network -t minha-base:1.0 .
# Arrancar a partir dela
delonix vm create teste --disk minha-base:1.0 --ssh-key @~/.ssh/id_ed25519.pub
# …ou a partir de um qcow2 publicado por ti, sem passar pelo store
delonix vm create outra --url-img https://o-teu-bucket/imagem.qcow2
delonix vm destroy teste; cd ..; rm -rf lab-vm
Verificação: o build imprime
[1/1] stage-1: FROM ubuntu:24.04 e verifica o checksum da cloud
image. Com --url-img, se não houver <url>.sha256
publicado, o motor diz que está a confiar só no TLS — em vez de calar.
A chave que injectas vai para a conta delonix, não para a conta
default da distro — o bloco de próximos passos do vm create
imprime o ssh exacto.
Arnês reproduzível (§7): este lab corre sozinho,
com asserção de resultado funcional — lê de
vm image ls -o json que a imagem minha-base:1.0 ficou no
store, de vm ls -o json que uma VM arranca Running com
IPv4 a partir dela, e prova que vm destroy
não deixa órfãos e que a imagem sobrevive ao
destroy (o overlay era por-VM) — falha em voz alta distinguindo
«imagem não construiu» de «VM não arrancou» de «órfão», em
docs/labs/lab-6/: prepare → run → verify → cleanup
(bash docs/labs/lab-6/lab.sh). Precisa de libguestfs
(virt-customize) no host; o arnês gera a sua própria chave SSH e
aponta o SSHKEY do scaffold para ela. Baseline histórica — validado em delonix 5.1.1 (Linux, libguestfs + libvirt, rootless) a 2026-10-24; não re-medido na linha 0.1.0 actual.
Goal: build your own qcow2 image, the same way you build a container image.
mkdir -p lab-vm && cd lab-vm
# Scaffold that BUILDS AS-IS — delete what you don't need
delonix vm image init --name my-base
cat VMfile
# Needs libguestfs on the host: sudo apt install libguestfs-tools
delonix vm image build -t my-base:1.0 .
delonix vm ls
# A RUN with `apt-get install` needs guest networking, so ask for it:
# delonix vm image build --network -t my-base:1.0 .
# Boot from it
delonix vm create test --disk my-base:1.0 --ssh-key @~/.ssh/id_ed25519.pub
# …or from a qcow2 you published yourself, bypassing the store
delonix vm create other --url-img https://your-bucket/image.qcow2
delonix vm destroy test; cd ..; rm -rf lab-vm
Verification: the build prints
[1/1] stage-1: FROM ubuntu:24.04 and verifies the cloud image's
checksum. With --url-img, if there's no published
<url>.sha256, the engine says it's trusting TLS
alone — instead of staying quiet. The key you inject goes to the
delonix account, not the distro's default account — the
"next steps" block from vm create prints the exact
ssh command.
Reproducible harness (§7): this lab runs on its
own, asserting a functional result — it reads from
vm image ls -o json that the minha-base:1.0 image
landed in the store, from vm ls -o json that a VM boots
Running with an IPv4 from it, and it proves
vm destroy leaves no orphans and that the image
survives the destroy (the overlay was per-VM) — it
fails loudly, telling "image did not build" apart from "VM never
started" apart from "orphan", under docs/labs/lab-6/:
prepare → run → verify → cleanup
(bash docs/labs/lab-6/lab.sh). Needs libguestfs
(virt-customize) on the host; the harness generates its own SSH key
and points the scaffold's SSHKEY at it. Historical baseline — validated on delonix 5.1.1 (Linux, libguestfs + libvirt, rootless) on 2026-10-24; not re-measured on the current 0.1.0 line.
Materiais: guia escrito · arnês executável (prepare → run → verify → cleanup).
Materials: written guide · runnable harness (prepare → run → verify → cleanup).
Kubernetes sem DockerKubernetes with no Docker avançadoadvanced
Objectivo: um cluster local cujo runtime dos nós É o Delonix.
# 1. Preflight — falha em milissegundos se faltar a delegação de `cpu`,
# em vez de descarregar 425 MB para morrer aos 90 segundos
delonix system setup
# 2. O cluster
delonix cluster create --name lab
# 3. Falar com ele
export KUBECONFIG=~/.local/share/delonix/clusters/lab-kubeconfig.yaml
kubectl get nodes -o wide
# 4. Levar uma imagem TUA para dentro dos nós, sem registo nenhum
delonix build -t app:dev .
delonix cluster load app:dev --name lab
kubectl run app --image=app:dev --image-pull-policy=Never
# 5. A prova que interessa
kubectl get pod app -w
delonix delete clusters lab
Verificação: o passo 5 chega a
Running. Nem o ctr images import nem o
crictl images provam isto — os dois já reportaram sucesso sobre
uma imagem que o kubelet depois não resolvia.
Goal: a local cluster whose node runtime IS Delonix.
# 1. Preflight — fails in milliseconds if `cpu` delegation is missing,
# instead of downloading 425 MB just to die at the 90-second mark
delonix system setup
# 2. The cluster
delonix cluster create --name lab
# 3. Talk to it
export KUBECONFIG=~/.local/share/delonix/clusters/lab-kubeconfig.yaml
kubectl get nodes -o wide
# 4. Get an image of YOURS into the nodes, with no registry at all
delonix build -t app:dev .
delonix cluster load app:dev --name lab
kubectl run app --image=app:dev --image-pull-policy=Never
# 5. The proof that matters
kubectl get pod app -w
delonix delete clusters lab
Verification: step 5 reaches
Running. Neither ctr images import nor
crictl images prove this — both have reported success on an
image the kubelet then couldn't resolve.
IaC de verdade: plan, apply convergente, e a recriação recusadaReal IaC: plan, converging apply, and a recreate that gets refused avançadoadvanced
Objectivo: o ciclo completo — plan → apply →
plan outra vez (sem alterações) → mudar um campo QUENTE → mudar um
campo FRIO — sem ficheiro de estado nenhum: o que já foi
aplicado vive no PRÓPRIO recurso, não num .tfstate à parte.
mkdir -p lab-iac && cd lab-iac
cat > stack.yaml <<'EOF'
apiVersion: delonix.io/v1
kind: Network
metadata:
name: iac-lab
spec: {}
---
apiVersion: delonix.io/v1
kind: Container
metadata:
name: iac-app
spec:
image: nginx:alpine
network: iac-lab
memory: 64M
EOF
# 1. Nada foi aplicado ainda — o plano di-lo, e o exit code também
delonix stack plan -f stack.yaml --detailed-exitcode; echo "exit=$?" # 2 = há alterações
# 2. Aplicar
delonix stack apply -f stack.yaml
# 3. O MESMO plano, sobre o que acabou de ser criado
delonix stack plan -f stack.yaml --detailed-exitcode; echo "exit=$?" # 0 = nada a fazer
# 4. Mudar um campo QUENTE — o plano propõe uma actualização, não uma recriação
sed -i 's/memory: 64M/memory: 128M/' stack.yaml
delonix stack plan -f stack.yaml # memory: 64M → 128M
delonix stack apply -f stack.yaml # "updating 1 field(s) live" — MESMO pid
# 5. Mudar um campo FRIO (a imagem) — o apply RECUSA-SE, não adivinha
sed -i 's/image: nginx:alpine/image: httpd:alpine/' stack.yaml
delonix stack apply -f stack.yaml # refused: does not converge live: image
# 6. Só com a decisão explícita é que recria
delonix stack apply -f stack.yaml --replace Container/iac-app
delonix container rm -f iac-app; delonix network rm iac-lab; cd ..; rm -rf lab-iac
Verificação: no passo 4, o pid antes e depois
(delonix container inspect iac-app | grep pid) é o MESMO — só o
cgroup mudou. No passo 5 o apply sai com erro e nada
muda — nem o container antigo é tocado — até correres o passo 6 de
propósito. É a diferença entre uma ferramenta que converge e uma que só sabe
criar: um terraform apply sobre um campo imutável também
recria; a diferença aqui é que a recriação nunca é silenciosa, precisa do
nome exacto do recurso, e sem ela o comando falha em vez de decidir por ti.
Para remover o que o ficheiro deixou de declarar (sem tocar no resto), existe
ainda stack apply --prune — não corrido aqui de propósito, para
este lab nunca apagar mais do que os dois recursos que ele próprio criou.
Goal: the full cycle — plan → apply →
plan again (nothing to do) → change a HOT field → change a COLD
field — with no state file at all: what's already applied
lives on the resource ITSELF, not in a .tfstate off to the side.
mkdir -p lab-iac && cd lab-iac
cat > stack.yaml <<'EOF'
apiVersion: delonix.io/v1
kind: Network
metadata:
name: iac-lab
spec: {}
---
apiVersion: delonix.io/v1
kind: Container
metadata:
name: iac-app
spec:
image: nginx:alpine
network: iac-lab
memory: 64M
EOF
# 1. Nothing applied yet — the plan says so, and so does the exit code
delonix stack plan -f stack.yaml --detailed-exitcode; echo "exit=$?" # 2 = changes pending
# 2. Apply it
delonix stack apply -f stack.yaml
# 3. The SAME plan, against what was just created
delonix stack plan -f stack.yaml --detailed-exitcode; echo "exit=$?" # 0 = nothing to do
# 4. Change a HOT field — the plan proposes an update, not a recreate
sed -i 's/memory: 64M/memory: 128M/' stack.yaml
delonix stack plan -f stack.yaml # memory: 64M → 128M
delonix stack apply -f stack.yaml # "updating 1 field(s) live" — SAME pid
# 5. Change a COLD field (the image) — apply REFUSES, it doesn't guess
sed -i 's/image: nginx:alpine/image: httpd:alpine/' stack.yaml
delonix stack apply -f stack.yaml # refused: does not converge live: image
# 6. Only an explicit decision recreates it
delonix stack apply -f stack.yaml --replace Container/iac-app
delonix container rm -f iac-app; delonix network rm iac-lab; cd ..; rm -rf lab-iac
Verification: in step 4, the pid before and
after (delonix container inspect iac-app | grep pid) is the
SAME — only the cgroup changed. In step 5 apply exits with an
error and nothing changes — not even the old container is
touched — until you deliberately run step 6. That's the difference between a
tool that converges and one that only knows how to create: a
terraform apply over an immutable field also recreates; the
difference here is that the recreate is never silent, it needs the exact
resource name, and without it the command fails instead of deciding for you.
To remove what the file no longer declares (without touching anything else),
there's also stack apply --prune — not run here on purpose, so
this lab never deletes more than the two resources it created itself.