get2knowio / get2knowio/deacon
feat(set-up): in-place mode (--self) — run the personalization lifecycle inside a daemonless container
- Dominant language
- Rust
- Stars
- 2
- Forks
- 0
- Avg merge
- 2h 36m
- Merged PRs (30d)
- 78
Description
## Summary
Add an **in-place mode** to the planned `set-up` verb — `deacon set-up --self` — that runs the devcontainer personalization lifecycle *from inside the container being configured*, with no container runtime available. Companion: a `deacon build --emit-metadata-file ` flag that bakes the merged configuration into the image so in-place mode has a config source without a daemon to inspect.
## Motivation
The devcontainer lifecycle increasingly gets split across actors that don't share a Docker daemon:
1. **Image assembly** happens in CI (`deacon build` → registry).
2. **Container creation** happens via an orchestrator API — Kubernetes, ECS/Fargate, Cloud Run, Nomad — not by a CLI that owns a daemon.
3. **In-container personalization** (lifecycle hooks, `remoteUser`/`remoteEnv`, `userEnvProbe`, dotfiles) still has to happen at container start — and *cannot* happen at build time, because the workspace typically arrives as a runtime volume mount, so `postCreateCommand` has nothing to run against until the container exists.
Today no devcontainer CLI can do phase 3 in these environments: the reference CLI's `set-up` adopts an existing container but still drives it via `docker exec` from outside, which requires a reachable daemon. On daemonless runtimes (Fargate is the clearest example — no privileged containers, no Docker socket, period) the personalization phase simply has no owner, and every platform builder reimplements hooks/dotfiles/env-probe semantics by hand in entrypoint scripts.
deacon is unusually well-placed to close this gap: it ships as a small static (musl) binary that can be `COPY`'d into any image, and the needed machinery — `ContainerLifecycle`, `run-user-commands`, dotfiles, env probe, the `state` module — already exists behind the `ContainerRuntime` abstraction.
## Proposal
### 1. `deacon set-up --self` (in-place mode)
A `LocalRuntime` implementation of the existing `ContainerRuntime` trait where the target container is the calling process's own environment: "exec in container" = spawn a local process, "copy into container" = local file write. `set-up --self` then reuses the same lifecycle orchestration as the daemon-backed `set-up`:
- run `onCreateCommand` / `updateContentCommand` / `postCreateCommand` / `postStartCommand` (and `postAttachCommand` per the same rules `set-up` uses),
- apply `remoteEnv`, resolve `remoteUser`, run `userEnvProbe`,
- install dotfiles,
- emit the same `{outcome, configuration?, mergedConfiguration?}` result document as `set-up`.
Intended use: a container image whose entrypoint runs `deacon set-up --self && exec ` under any orchestrator.
### 2. Config source without a daemon
Inside the container there is nothing to `docker inspect`, so the `devcontainer.metadata` image label is unreadable. Resolution order for `--self`:
1. `--config ` explicitly;
2. a metadata file baked at build time (see below);
3. `devcontainer.json` discovered in the (mounted) workspace folder.
New build flag: `deacon build --emit-metadata-file ` writes the merged configuration JSON (the same content as the `devcontainer.metadata` label, array form) to a well-known path in the image, e.g. `/usr/local/share/deacon/metadata.json`. This keeps in-place mode fully portable — no dependence on any orchestrator's metadata endpoint.
### 3. Idempotency state on a configurable path
`onCreateCommand` must run once per *workspace*, `postStartCommand` on every start — but in orchestrated environments the container filesystem is ephemeral while the workspace volume persists. In-place mode needs the run-once marker directory to be configurable (e.g. `--container-data-folder` honored locally, defaulting somewhere under the workspace/persistent volume) so the existing `state` semantics survive container replacement.
## Non-goals
- **No orchestrator awareness.** No Kubernetes/ECS/Cloud Run APIs, no task/pod creation, no registry auth logic. deacon stays a consumer-side devcontainer CLI on its own side of the container boundary; orchestration is the caller's problem.
- **No build-in-container.** `--self` personalizes an existing environment; it never builds images or installs Features at runtime (which would recreate issue #1's anti-pattern). Features are baked at `deacon build` time.
- **No divergence from `set-up` semantics.** `--self` is the same verb with a different transport; behavior differences beyond "no daemon required" are bugs.
## Relationship to existing work
- Rides on the already-planned `set-up` parity item (`docs/ROADMAP_TO_1.0.md` §3.A.1) — this is an additive transport mode, best implemented alongside it.
- Fits the `DIFFERENTIATORS.md` bar: a net-positive capability the reference CLI does not have, while staying spec-compliant (the lifecycle contract executed is unchanged).
- Complements lockfile graduation (roadmap B.1): `build --frozen-lockfile` in CI plus `set-up --self` at start gives a fully reproducible prebuilt-image pipeline.
## Acceptance sketch
- In a plain `docker run` container (no socket mounted) whose image contains deacon and a baked metadata file, `deacon set-up --self` runs all applicable lifecycle phases, applies `remoteEnv`/dotfiles, and exits 0 with the standard result document.
- Second invocation against the same persistent workspace skips `onCreateCommand`, re-runs `postStartCommand`.
- `--self` with no baked metadata falls back to the workspace `devcontainer.json`; with neither, it fails with an actionable error naming the three config sources.
- `deacon build --emit-metadata-file` produces a file byte-equivalent in content to the image's `devcontainer.metadata` label.
Contributor guide
Research direction
Start with the existing set-up implementation, the ContainerRuntime abstraction, ContainerLifecycle, and the state module, then review docs/ROADMAP_TO_1.0.md §3.A.1. Trace how daemon-backed execution resolves configuration and lifecycle phases before designing the LocalRuntime transport and build metadata emission. Done means --self supports the stated config fallback and idempotency behavior, while build --emit-metadata-file matches the image metadata content and the standard result document is preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, rust
- Domain
- cli, cloud, devops
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100