Instruction replay¶
The CLI test of web-app-docs reads the Production block out of the running documentation site and executes it, so the published instructions are proven rather than reviewed.
Cast¶
What it is |
What runs there |
|
|---|---|---|
🖥️ Stack host |
the worker the outer play already runs on |
|
🎯 Target |
a throwaway container, project |
systemd and an ssh server, nothing else until the deploy lands; a host install also runs there |
🛰️ Resolver |
a CoreDNS sidecar in the target’s network namespace |
answers every name under |
🐳 Recipe container |
started by the block’s own |
the deploy that the instructions tell a reader to run |
The target is what the instructions call “a managed server”. It is deliberately bare: everything else it ends up with arrives through the deploy under test.
Its resolver is the one exception. DOCS_TEST_DOMAIN is the outer stack’s own domain, and the stack daemon hands every bridge container the outer stack’s CoreDNS, so without the sidecar the target resolves the domain it is deploying to the outer application container. The sidecar answers the whole domain with the target’s bridge address, and with that address rather than 127.0.0.1 so that containers the deploy starts inside the target reach the same vhosts.
A role that ships no container stack publishes a host install instead of a recipe: a block that opens with git clone and configures the machine it runs on. The target is that machine, so the block is replayed inside it over the checkout mounted at DOCS_TEST_SRC_DIR, and the two clone lines are dropped so the replay reads the tree under test. A block that neither starts a container nor clones the repository fails the test.
Schema¶
flowchart TD
subgraph HOST["🖥️ stack host"]
direction TB
TEST["1️⃣ 🧪 test.sh<br>returns early unless DOCS_TEST_MODE=compose"]
EXTRACT["2️⃣ 📖 extract.py<br>reads the block off the live docs site"]
MIRRORED["🪞 mirror.sh<br>--mirror when CI configured a GHCR mirror"]
FILL["3️⃣ 🔑 ed25519 pair + placeholder fill<br>everything except HOST"]
MACHINE["4️⃣ 🚜 machine.sh"]
ADDR["6️⃣ 📍 the target's bridge address"]
DNS["7️⃣ 🛰️ Corefile.tmpl rendered, resolver up"]
SHAPE{"🔀 docker run or git clone"}
GUARD["9️⃣ 🚦 HOST fill + placeholder guard"]
BLOCK["🔟 ▶️ bash -e block.sh"]
end
subgraph TARGET["🎯 target container"]
direction TB
INIT["5️⃣ ⚙️ systemd, no engine<br>Dockerfile + init.sh"]
RESOLV["🛰️ /etc/resolv.conf names the sidecar<br>*.DOCS_TEST_DOMAIN answers with the bridge address"]
SSHD["8️⃣ 🔐 sshd.sh<br>ssh server + run key for root"]
HOSTRUN["▶️ bash -e block.sh in the mounted checkout<br>clone lines dropped"]
DEPLOYED["📦 docker-ce and the role"]
FAILED["⏹️ systemctl --failed + journalctl -xeu"]
end
RECIPE["🐳 docker run <deploy image><br>on the stack daemon"]
TEST --> EXTRACT --> MIRRORED --> FILL --> MACHINE
MACHINE -->|"🌉 compose up machine"| INIT
INIT -->|"⏳ await systemd"| ADDR
ADDR --> DNS
DNS -->|"🌉 compose up resolver"| RESOLV
RESOLV --> SHAPE
SHAPE -->|"🐳 recipe"| SSHD
SHAPE -->|"🏠 host install"| HOSTRUN
SSHD --> GUARD --> BLOCK
BLOCK --> RECIPE
RECIPE -->|"🔗 ssh as root"| DEPLOYED
HOSTRUN --> DEPLOYED
DEPLOYED -.->|"❌ non-zero replay"| FAILED
MACHINE -.->|"🧹 trap EXIT"| TARGET
classDef host fill:#e8f0fe,stroke:#1a73e8,color:#102a43;
classDef target fill:#e6f4ea,stroke:#137333,color:#0b2e13;
classDef pivot fill:#fef7e0,stroke:#b06000,color:#3d2600;
classDef recipe fill:#f3e8fd,stroke:#8430ce,color:#2a0b45;
class TEST,EXTRACT,MIRRORED,FILL,MACHINE,BLOCK host;
class INIT,RESOLV,SSHD,HOSTRUN,DEPLOYED,FAILED target;
class SHAPE,ADDR,DNS,GUARD pivot;
class RECIPE recipe;
🔵 stack host · 🟢 target · 🟣 the recipe’s own container · 🟠 the steps that bridge them
Flow¶
# |
Step |
Detail |
|---|---|---|
1️⃣ |
🧪 |
returns early unless the deployment mode is compose |
2️⃣ |
📖 |
fetches the role’s page over |
🪞 |
the GHCR mirror is attached |
|
📏 |
the stack’s disc-space limit is carried over |
|
3️⃣ |
🔑 key pair and placeholders |
an ed25519 pair per run under |
4️⃣ |
🚜 |
|
5️⃣ |
⚙️ the target boots |
image built from |
6️⃣ |
📍 the bridge address is read |
|
7️⃣ |
🛰️ the resolver takes over the domain |
|
🔀 |
the shape decides where the block runs |
|
8️⃣ |
🔐 |
installs an ssh server, authorizes the run’s public key for |
9️⃣ |
🚦 |
the guard fails on any remaining |
🏗️ |
the local image is built |
with |
🔟 |
▶️ the block executes |
the recipe shape runs it on the stack host, where its |
⏹️ |
the evidence of a failed replay |
a non-zero block prints |
🧹 |
teardown |
|
Environment¶
Variable |
Source |
Use |
|---|---|---|
|
|
documentation site the block is read from |
|
|
role whose page carries the instructions; names the compose project |
|
|
deployment mode; the test runs only for compose |
|
|
the stack host’s |
|
|
fills |
|
|
fills |
|
|
checkout holding the generated |
|
|
wait for the on-demand documentation build |
|
|
holds the replay key pair and the rendered |
|
|
public half, consumed by |
|
|
names the GHCR repository the mirror lives under; empty outside CI, which leaves the block unmirrored |
|
|
names the GHCR namespace; empty outside CI |
|
|
path segment separating mirrored images from the repository’s own |
|
|
|
Files¶
File |
Role |
|---|---|
|
entry point; extraction, key pair, placeholder fill |
|
pulls the Production block off the live documentation site |
|
points the extracted block’s provision call at the GHCR image mirror when CI configured one |
|
hands the extracted block’s provision call the stack host’s disc-space limit |
|
target lifecycle, wildcard resolution, |
|
the target service on the default bridge with a named volume at |
|
the resolver’s configuration; |
|
builds the target from the distribution base image |
|
installs systemd into that image, and deliberately no container engine |
|
ssh server and the run’s authorized key, piped into the running target |