get2knowio / get2knowio/deacon

feat(set-up): in-place mode (--self) — run the personalization lifecycle inside a daemonless container

Open
#650 0 comments 0 reactions 0 assignees View on GitHub
feat
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.