Add container dev mode guide and field note - #473
Conversation
Capture the disposable-QEMU engine-VM lab that exercised Container Dev Mode's authenticated push path end-to-end (single-layer sync, plus the two bugs the lab caught: plain-HTTP write path and the 2 MiB body limit). Kept draft: true with [ENGINEER:] gaps because the CLI and device extension are pre-release; a reader cannot reproduce it until the feature ships. Flip draft to false once it lands and the steps are re-verified on a released build. Signed-off-by: Javier Tia <javier@peridio.com>
The Container Dev Mode VM-push draft carried 14 em dashes; the sibling field notes use at most one, so the section's house style is plain hyphens. Convert them now so the prose matches before the draft reaches the correctness review gate, leaving the release-blocked [ENGINEER:] placeholders untouched. Signed-off-by: Javier Tia <javier@peridio.com>
Docusaurus rejects `image: ''` ("does not look like a valid url"), so the
dev server refused to compile the note. The image field is optional -
omitting it renders the index placeholder box - so remove the empty
value rather than invent a path before the note has real art.
Signed-off-by: Javier Tia <javier@peridio.com>
Container Dev Mode ships a new CLI surface (a container_dev config block and five avocado container dev subcommands) plus a device-side agent extension, and nothing in the docs told a user how to use any of it. The only written material was a field note about how we validated the push path internally, which is not a how-to. Add a guide under Development covering prerequisites, the config block, the loop, and reference tables for the commands, environment variables, and default ports. It sits next to hardware-in-the-loop because it is the same iterate-against-a-live-device bargain applied to containers, and it states the dev-only scope up front so nobody mistakes it for a production container delivery path. Signed-off-by: Javier Tia <javier@peridio.com>
The draft told the story from the implementer's chair: how a throwaway QEMU lab validated the authenticated push path. A reader learned how we tested the registry internals, not what the feature does for them or why they would want it, so the note failed the audience it exists for. Lead with the loop the reader already lives in - a multi-gigabyte inference container where one iteration costs the size of the image rather than the size of the change - and show the config block, the three commands, and what actually moves. The QEMU lab and the two bugs it caught become the credibility half rather than the premise, and the how-to now lives in the guide, which the note links to instead of restating. Drops the remote-build bullet: an NFS/SQLite detail about our own build infrastructure that told the reader nothing about the feature. Signed-off-by: Javier Tia <javier@peridio.com>
Both pages described the hot-reload loop in prose and a numbered list, which made the reader assemble the shape themselves: where the layer crosses, what runs on the host versus the device, and that the cycle closes back to the next edit. The device's role as a live participant was the part most easily missed. Add the same diagram to the guide and the note, drawn as a text block with the repo's existing box-drawing and magic-comment colors rather than a new Mermaid dependency, since six pages already use that convention and the prism magic comments work in blog posts too. Colors follow core-concepts: orange for what you do, green for the CLI, blue for on-device. It also makes explicit that a QEMU target and a physical board are the same thing to this loop, both being SSH-reachable devices. Signed-off-by: Javier Tia <javier@peridio.com>
The docs CI runs prettier --check, and neither file was formatted: the
reference tables in the guide were unaligned, and prettier reflowed the
note's PullQuote.
Running prettier also exposed a real defect. Prettier's markdown parser
reads the asterisks in an MDX `{/* ... */}` comment as emphasis and
rewrites them to underscores, producing `{/_ ... _/}`, which MDX then
tries to evaluate as a JS expression and fails on with "Could not parse
expression with acorn". The pre-release note therefore moves into the
frontmatter as YAML comments, which prettier preserves and which never
render, next to the existing internal-drafting comment block.
Signed-off-by: Javier Tia <javier@peridio.com>
There was a problem hiding this comment.
🟡 Not ready to approve
The new guide has a confirmed docs-link convention mismatch and an internally inconsistent config-key reference table that should be corrected for clarity and consistency.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds end-user documentation for the new “Container Dev Mode” feature by introducing a dedicated Development guide and a supporting (draft) field note, and wires the guide into the Guides sidebar.
Changes:
- Add a new “Container dev mode” guide describing setup, workflow loop, command/env/config references, ports, and trust model.
- Add a new draft field note reframing the feature around the developer inner loop and linking readers to the guide.
- Update the Guides sidebar to include the new guide under Development.
File summaries
| File | Description |
|---|---|
| src/sidebars-guides.js | Adds the new guide doc ID to the Development section navigation. |
| src/field-notes/2026-07-23-container-dev-mode-vm-push.mdx | New draft field note describing the workflow and validation story, linking to the guide. |
| src/docs-guides/container-dev-mode.md | New end-user guide documenting Container Dev Mode usage and reference material. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| | `runtimes.<name>.container_dev` | yes | Presence of the block enables the feature for that runtime. | | ||
| | `container_dev.images[].ref` | yes | Image reference (`repository[:tag]`) watched on the host engine. | | ||
| | `container_dev.images[].service` | yes | Device service that consumes the image and is restarted after a pull. | | ||
| | `container_dev.registry.port` | no | Port for the bulk read listener. Defaults to `5599`. | |
There was a problem hiding this comment.
Agreed. Row 1 is runtimes.<name>.container_dev and rows 2-4 drop the prefix (container_dev.images[].ref, .images[].service, .registry.port), so the table reads as if the later keys sit at the top level of the file rather than under the same runtime.
Fully qualifying all four is the fix. Queuing it with the other changes on this PR.
jetm
left a comment
There was a problem hiding this comment.
Cold second read on my own PR. Six findings inline, plus both of Copilot's are valid and I am queuing fixes for them.
The one worth stopping on: the guide has no draft/unlisted and is wired into the sidebar, so merging publishes it to the live docs - while the field note in this same PR is held at draft: true with a comment saying the feature has not shipped and a reader cannot reproduce it today. That reasoning applies to the guide unchanged. container_dev is on zero files on avocado-cli origin/main and 15 on the unmerged branch, and a production build emits the guide's HTML while emitting nothing for the note.
Verified by building: npm run build exits 0. Worth noting explicitly that this is weak evidence for the note and not for the guide - Docusaurus drops draft: true posts in production, so the passing build validates the guide and says nothing at all about the note.
Also checked and clean: all 5 avocado container dev subcommands, all 6 AVOCADO_CONTAINER_DEV_* env vars, all 4 config keys, the three default ports, the sidebar id, every internal link under onBrokenLinks: 'throw', the highlight directives, the truncate marker position, authors: [jtia], and the date/filename match.
| @@ -0,0 +1,216 @@ | |||
| --- | |||
| sidebar_position: 3 | |||
There was a problem hiding this comment.
This guide publishes to production for a feature that has not shipped, while its own companion note is gated for exactly that reason.
The frontmatter here has no draft or unlisted, and src/sidebars-guides.js:34 registers it, so merging publishes it live. The field note in this same PR carries draft: true with the comment "PRE-RELEASE feature - keep on the branch until Container Dev Mode ships" and "a reader cannot reproduce this today."
That reasoning applies to the guide verbatim. Checked the CLI: git grep container_dev in avocado-cli returns 0 files on origin/main and 15 only on the unmerged origin/container-dev-mode branch. A production build confirms the asymmetry - it emits developer-reference/container-dev-mode.html containing avocado container dev up, and emits nothing at all for the note.
So a reader lands on the live page and runs a subcommand that exists on no released CLI. Gate the guide the same way the note is gated, or hold the whole PR until the CLI lands.
| runtimes: | ||
| dev: | ||
| extensions: | ||
| - avocado-dev |
There was a problem hiding this comment.
avocado-dev is not a real extension name, and this example does not define it.
The shipped prebuilt extension is avocado-ext-dev - 67 files under src/ use that spelling. The bare avocado-dev appears in exactly one other doc, hardware-in-the-loop.md:30, and it is legal there only because that same file defines it inline at line 43 under a top-level extensions: block.
This guide copied the usage and dropped the definition, so a reader who pastes this avocado.yaml and runs avocado build gets Extension 'avocado-dev' not found in configuration (it resolves defined: false, per config_show.rs:277-286). Confirmed it reaches the rendered page: avocado-dev appears once in the built HTML, avocado-ext-dev zero times.
Either rename it to avocado-ext-dev or add the inline definition block the way hardware-in-the-loop.md does.
| avocado container dev status | ||
| ``` | ||
|
|
||
| `status` reports registry, watcher, and last-sync state. It also surfaces the case where a device presents a stale token and needs `up` to be re-run to re-bootstrap. |
There was a problem hiding this comment.
The stale-token sentence describes behavior the CLI explicitly does not have yet.
The first half of this paragraph (registry, watcher, last-sync) is accurate. The claim that status "surfaces the case where a device presents a stale token and needs up to be re-run" is not.
avocado-cli src/commands/container/dev.rs:26-36 says so in its own words: the per-device status.devices list and the needs_rebootstrap() rotation are "NOT YET LIVE, despite being implemented and tested in bootstrap.rs", because "up writes session.json once and never updates it, so devices stays empty and needs_rebootstrap() is structurally false".
A reader with a genuinely stale token runs status, sees nothing flagged, and concludes the session is healthy while pulls fail. Drop the sentence until the path is wired.
| targets={['qemux86-64']} | ||
| cli="1.0.0-rc.1" | ||
| reference={{ | ||
| label: '[ENGINEER: reference link once the code is public]', |
There was a problem hiding this comment.
This placeholder is inside a component prop, so it renders as link text rather than sitting in prose.
It is passed as reference.label to <TestStatus>, and src/components/TestStatus/index.jsx:45-48 renders that as the anchor text. The moment draft flips to false - or on any dev-server or deploy-preview render, neither of which honors draft - the card at the top of the note shows a GitHub-iconed link reading [ENGINEER: reference link once the code is public], pointing at the bare avocado-linux/references repo root.
Every sibling note points at a real /tree/main/<reference-name> subpath. This is the only placeholder in the corpus that lands in a component prop instead of body prose, which is also why a read-through of the rendered page is less likely to catch it.
|
|
||
| ## Reproduce it | ||
|
|
||
| [ENGINEER: Container Dev Mode is pre-release - fill this in once the CLI and the |
There was a problem hiding this comment.
The whole "Reproduce it" section is still an editorial placeholder.
Six lines of [ENGINEER: ...] drafting notes naming setup-lab.sh and verify-vm-write-path.sh, with no link to either, standing where the runnable instructions go.
Correctly gated today by draft: true, so this is a blocker on the un-draft rather than on the merge - flagging it so it does not get lost when the feature ships. CONTRIBUTING.md:124: "The draft only becomes 'ready' when no [ENGINEER: ...] placeholders remain."
| # PRE-RELEASE NOTE (not for readers): Container Dev Mode is not shipped yet. | ||
| # The framing and the measured results are real, but the CLI and the device | ||
| # extension are pre-release, so a reader cannot reproduce this today. It stays | ||
| # draft:true on this branch until the feature ships and the [ENGINEER: ...] |
There was a problem hiding this comment.
This frontmatter comment will trip the pre-merge placeholder gate forever.
The comment contains the literal string [ENGINEER: ...], and the documented gate at CONTRIBUTING.md:126-128 is grep -l "\[ENGINEER:" *.mdx. So even after the two real placeholders (lines 30 and 151) are filled, this file keeps matching.
The next person either hunts for a placeholder that no longer exists, or learns to ignore the gate for every note - which is the worse outcome. Rewording to something like "the ENGINEER gaps" clears it without losing the meaning.
…nk extension The configuration reference gave the first key as `runtimes.<name>.container_dev` and then dropped that prefix for the three below it. Read on its own, `container_dev.images[].ref` looks like a top-level key - which the same page warns three sections earlier is exactly what does not work: "the presence of a `container_dev` block under a runtime turns it on for that runtime. A block placed anywhere else is not honored." So the table quietly contradicted the prose it was meant to summarize. Qualify all four rows. The paths match the CLI's own types - `ContainerDevConfig` carries `images` and `registry`, `ContainerDevImage` carries `ref` and `service` - so a reader can paste the dotted path against the YAML example above and land in the right place. The intro also linked `./hardware-in-the-loop.md` where every other link in docs-guides is extensionless; seven of them are, and this was the only one that was not. Beyond consistency, the extension is the part that breaks if a target is ever renamed to `.mdx`. Prettier reformatted the table's column widths after the longer keys went in. Worth noting the file passed `prettier --check` before this change and the hand-aligned padding broke it, so the reformat is part of the fix rather than incidental churn - checked both before and after with the repo's own `src/.prettierrc.json`. Signed-off-by: Javier Tia <javier@peridio.com>
There was a problem hiding this comment.
🟡 Human review recommended
The documentation is explicitly pre-release and dependent on multiple external PRs/releases, so correctness and publish-readiness can’t be fully validated from this change set alone.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
…vent The guide's headline promise is that a normal `docker build` triggers the reload, and the watcher genuinely does read tag events from the engine event stream. But BuildKit emits no tag event when it tags a build result, and BuildKit is the default builder on current Docker, so following the guide as written leaves the reader watching a device that never updates with nothing in any log to explain it. Document both escape hatches at the point the reader builds, and recommend `container dev sync` over `DOCKER_BUILDKIT=0`: Docker already prints a deprecation notice for the classic builder, so the flag buys time rather than a fix, while the explicit trigger does not consult the event stream at all. Verified both paths against the lab VM before writing them down. The architecture section previously asserted the event fires; it now points here instead, so the promise and the caveat cannot drift apart. Signed-off-by: Javier Tia <javier@peridio.com>
There was a problem hiding this comment.
🟢 Ready to approve
The documentation changes are internally consistent, follow existing docs conventions, and introduce no verified build/MDX issues in the modified files.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
… it needs Every code block on the page was labelled "On Host", and the page never once showed a command run on the device. A reader following it therefore had no way to learn where their container actually runs, and the first thing they try when the loop looks stuck - `docker logs my-app` on their workstation - returns "No such container", which reads as a broken feature rather than as the wrong machine. Name the two sides up front and borrow the vocabulary the site already has: the hardware-in-the-loop guide does for extensions what this page does for containers, uses "target" for the far side, and is already linked from the intro. Calling it the HIL target rather than "the device" ties the two pages together and says which machine is meant without a reader having to infer it. The new section carries the `docker logs` case explicitly, with the target-side commands that do work. The page also never stated that the `service` it restarts has to exist already. Nothing said who creates it, so the reasonable reading is that container dev mode does, and a reader hits an unexplained failure the first time they configure one. Say it in Prerequisites, and show the ExecStart shape there, because a unit that restarts a container instead of re-running `docker run` fails silently - the layer pulls, the restart succeeds, and the old image keeps running. Signed-off-by: Javier Tia <javier@peridio.com>
There was a problem hiding this comment.
🟡 Not ready to approve
A docs-guides link/terminology consistency issue should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
src/docs-guides/container-dev-mode.md:99
- This sentence mixes an extensionful relative doc link ("./hardware-in-the-loop.md") with the extensionless style used elsewhere in docs-guides, and also introduces the acronym "HITL" after consistently using "HIL" in this guide. Use an extensionless link and avoid the acronym here to keep terminology and link style consistent.
This is the same host/target split as [hardware in the loop](./hardware-in-the-loop.md), which does for extensions what this page does for containers. If you already run HITL, the mental model carries over unchanged.
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
…mons The caution said BuildKit emits no tag event, so the watcher cannot see a plain `docker build`. That was measured against one daemon, docker 20.10.24, and written up as a property of BuildKit. It is a property of the daemon version. Docker 29.6.2 emits `image tag` for a BuildKit build and the loop runs unattended end to end; 20.10.24 emits nothing. As written the page told most readers to work around a problem they do not have, and pushed them toward a builder Docker has deprecated. State the version boundary instead, give the one-line check for the daemon that runs their builds, and keep both workarounds for anyone actually on an old one. The exact release that changed is not claimed, because only those two endpoints were measured - pre-23 is called out as the conservative line. Signed-off-by: Javier Tia <javier@peridio.com>
There was a problem hiding this comment.
🟢 Ready to approve
Only minor documentation consistency issues were found (no functional or build-blocking problems identified in the reviewed changes).
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
src/docs-guides/container-dev-mode.md:99
- This link includes a
.mdextension and uses the acronymHITL, but the docs-guides corpus uses extensionless relative links (e.g.custom-kernel.mdlinks./hardware-in-the-loop) and this page otherwise usesHIL. Dropping the extension keeps links stable and avoids inconsistent terminology.
This is the same host/target split as [hardware in the loop](./hardware-in-the-loop.md), which does for extensions what this page does for containers. If you already run HITL, the mental model carries over unchanged.
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Problem
Container Dev Mode adds a new CLI surface (a
container_devconfig block, fiveavocado container devsubcommands) and a device-side agent extension, and nothingin the docs told a user how to use any of it. The only written material was a field
note about how the push path was validated internally, which is a QA story rather
than a how-to: a reader learned how the registry was tested, not what the feature
does for them.
Solution
Split the two jobs. A new guide under Development documents the feature for an
engineer who wants to use it, written against the implementation on the
container-dev-modebranches so the commands, config keys, ports and prerequisitesare the real ones. The field note is reframed to lead with the loop a developer
already lives in - a multi-gigabyte inference container where one iteration costs
the size of the image rather than the size of the change - and links to the guide
instead of restating it. The QEMU lab and the two bugs it caught become the
credibility half rather than the premise.
Key changes
docs-guides/container-dev-mode.md: new guide (prerequisites, config block, theloop, and reference tables for commands, env vars and default ports), plus its
sidebars-guides.jsentry next to Hardware in the loop.described our own build infrastructure rather than the feature.
block using the repo's existing box-drawing and magic-comment colors rather than
adding a Mermaid dependency.
{/* ... */}comment to underscores, producing
{/_ ... _/}, which MDX fails to parse("Could not parse expression with acorn"). The pre-release note moved into
frontmatter YAML comments, which prettier preserves.
Dependencies
This PR must not merge until every item below has landed. The guide documents
commands, config keys and prerequisites that do not exist in any released build
until they do, and merging early would publish documentation for a feature a reader
cannot use.
avocado container dev up/sync/status/down/prunecommands, theruntimes.<name>.container_devblock, the embedded registry, the engine watcher, and theAVOCADO_CONTAINER_DEV_*variables.avocado-ext-container-agent-devextension the guide lists as a device prerequisite. Without it the device has nothing to pull the layer or restart the service.AVOCADO_CONTAINER_DEV_VM.<TestStatus>, replacing the pre-release1.0.0-rc.1.All three PRs are open from their own
container-dev-modebranch; #184 and #46target
main, #246 targetsscarthgap. The CLI and the agent are two halves of oneprotocol, so neither is optional and there is no useful ordering between them; the
release is necessarily last.
Tracked in ENG-2224, which carries the ship-day checklist for this PR.
Reviewer notes
This PR is a draft on purpose. The field note is
draft: trueso it is excludedfrom the production build, but the guide is not: merging publishes it to
docs.peridio.com. Draft status makes an accidental merge impossible while the
dependencies above are outstanding.
Verification. Run in
src/against thecontainer-dev-modebranches ofavocado-cli and avocado-os:
npx prettier --checkon all three files: clean.npm run lint(eslint): clean.npm run build:[SUCCESS] Generated static files in "build". Confirmeddeveloper-reference/container-dev-mode.htmlis emitted and the field note isabsent from the build, so the draft flag does what it should.
Unverified content, flagged. The guide was written by reading the implementation
on the branches above, not by running a released binary: the documented surface
comes from
src/commands/container/{mod,dev}.rs,src/utils/container_dev/config.rs,bootstrap.rsand the extension'savocado.yaml(CLI1.0.0-rc.1, a pre-releasedev build). No command output is reproduced in the guide, because none of the
commands were run against a released build. Every command, config key and port needs
re-verification against the shipped CLI before this merges, and the note still
carries two
[ENGINEER: ...]gaps (the<TestStatus>reference link and theReproduce it section) that are release-blocked.