Skip to content

get2knowio/deacon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

195 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

deacon

The DevContainer CLI, minus the parts you don't use.

A fast, focused Rust CLI for developers who use dev containers and CI pipelines — not for feature authors.

Latest Release CI CodeQL Coverage MSRV Security Policy License: MIT

Install

curl -fsSL https://get2knowio.github.io/deacon/install.sh | bash
Other installation methods

macOS (Homebrew) - Coming Soon

brew install get2knowio/tap/deacon

Manual Download

Download from releases:

Platform Architecture Download
Linux x86_64 deacon-linux-x86_64.tar.gz
Linux ARM64 deacon-linux-arm64.tar.gz
Linux (musl) x86_64 deacon-linux-musl-x86_64.tar.gz
macOS x86_64 deacon-darwin-x86_64.tar.gz
macOS ARM64 (Apple Silicon) deacon-darwin-arm64.tar.gz
Windows x86_64 deacon-windows-x86_64.zip
Windows ARM64 deacon-windows-arm64.zip

From Source

git clone https://github.com/get2knowio/deacon.git
cd deacon
cargo build --release
./target/release/deacon --help

Installer Options

The install script supports these environment variables:

  • DEACON_VERSION — Specific version (default: latest)
  • DEACON_INSTALL_DIR — Install directory (default: ~/.local/bin)
  • DEACON_FORCE=true — Overwrite existing binary without prompt
# Install specific version
curl -fsSL https://get2knowio.github.io/deacon/install.sh | DEACON_VERSION=0.2.0 bash

Quick Start

# Start a dev container
deacon up

# Run a command in the container
deacon exec -- npm install

# Stop and remove the container
deacon down

Verify installation:

deacon --version

Shipped Commands

Command Description
up Create and start a dev container (features installed at build time via BuildKit)
down Stop and remove a dev container or compose project
exec Execute a command in a running container
build Build a dev container image (with feature layering for Dockerfile configs)
read-configuration Resolve and output devcontainer.json (with extends + variable substitution)
run-user-commands Run lifecycle commands in an existing container
set-up Convert an already-running container into a DevContainer (lifecycle + dotfiles + /etc patches)
upgrade Regenerate the lockfile from the currently resolved feature set
outdated Report current / wanted / latest feature versions
templates apply Scaffold a project from a template
config Configuration management subcommands
doctor Environment diagnostics and support bundle creation

Known limitations

Limitation Notes
Podman runtime Ships as experimental in 1.0. Trait-level integration is complete and the happy path works, but rootless-Podman parity items (label=disable, --userns=keep-id, --uidmap/--gidmap) and dedicated test coverage are targeted for 1.1 — see #30. Using --runtime podman emits a one-time WARN.
build features Feature installation during build is supported for Dockerfile-based configs only. Compose-build and image-reference configs still error out with features (different integration patterns; tracked as a post-1.0 follow-up).

For the full 1.0 roadmap, see docs/ROADMAP_TO_1.0.md. Post-1.0 hardening landed in May 2026 — redaction wiring into the tracing pipeline, a workspace-trust gate for host-side lifecycle hooks, async I/O conversion across crates/core, typed errors throughout crates/core, and the json5jsonc-parser migration (see closed #52).

Examples

Self-contained categorized examples live under examples/:

  • Configuration: variable substitution, lifecycle commands basics (examples/configuration/)
  • Container Lifecycle: lifecycle command execution, ordering, and variables (examples/container-lifecycle/)
  • Feature System: dependencies, parallelism, caching, and lockfile support (examples/features/)
  • Template Management: template application with options (examples/template-management/)

See the full details and additional commands in examples/README.md.

Runtime Selection

Deacon uses Docker as its container runtime. Podman support ships in 1.0 as experimental: the trait-level integration is complete, but rootless-Podman parity items (label=disable, --userns=keep-id, --uidmap/--gidmap) and dedicated test coverage are still required for full support, targeted for 1.1 (tracked in #30). Using --runtime podman emits a one-time WARN.

# Explicitly select Docker (optional, it's the default)
deacon --runtime docker up

# Experimental: select Podman
deacon --runtime podman up

# Or via environment variable
DEACON_RUNTIME=docker deacon up

Runtime Configuration

Logging

Deacon supports both human-readable text and structured JSON logging formats.

Text Logging (Default):

deacon --help  # Standard text output

JSON Logging:

export DEACON_LOG_FORMAT=json
deacon doctor  # Structured JSON logs for machine parsing

The JSON format is useful for CI/CD systems and log aggregation tools that need structured data.

Quiet mode and spinner (TTY only)

When running in a real terminal (stderr is a TTY) and using the default text output, deacon shows a small spinner during long-running operations like up and down. In these spinner sessions, if you haven't set DEACON_LOG, RUST_LOG, or --log-level, the default log level is temporarily set to warn so routine progress noise stays out of your way. JSON mode or non‑TTY environments (CI, redirections) do not render a spinner and keep the previous logging behavior.

Tips:

  • Want details with the spinner? Set RUST_LOG=info or use --log-level info.
  • Prefer structured logs? Use --log-format json (no spinner) and parse stderr.

Color and accessibility:

  • Help/usage output uses automatic color when writing to a terminal. Spinner/status messages also use subtle colors (yellow for in‑progress, green for success, red for failures).
  • Respecting your environment, color is disabled when not writing to a TTY and when NO_COLOR is set (see https://no-color.org/). To force-disable colors, export NO_COLOR=1.

PTY Allocation for JSON Log Mode

When using JSON logging format (--log-format json), lifecycle commands (onCreate, postCreate, etc.) run without PTY (pseudo-terminal) allocation by default. This is ideal for non-interactive scripts and automated environments.

However, if your lifecycle commands need interactive terminal behavior while maintaining structured JSON logs, you can force PTY allocation:

Via CLI Flag:

deacon up --log-format json --force-tty-if-json

Via Environment Variable:

# Enable PTY allocation
export DEACON_FORCE_TTY_IF_JSON=true
deacon up --log-format json

# Disable PTY allocation (default)
export DEACON_FORCE_TTY_IF_JSON=false
deacon up --log-format json

Truthy values (case-insensitive): true, 1, yes Falsey values: false, 0, no, or unset

Precedence:

  1. CLI flag (--force-tty-if-json)
  2. Environment variable (DEACON_FORCE_TTY_IF_JSON)
  3. Default (no PTY allocation)

Important Notes:

  • This setting only applies when --log-format json is active
  • With PTY allocation enabled, interactive commands work correctly while JSON logs remain structured on stderr and machine-readable output stays on stdout
  • Without PTY allocation (default), lifecycle commands run in non-interactive mode

Output Streams

Deacon follows a strict stdout/stderr separation contract to ensure reliable machine-readable output:

Stream Usage Contract

  1. JSON Output Modes (--output json, --json flags):

    • stdout: Single JSON document (newline terminated), nothing else
    • stderr: All logs, diagnostics, and progress messages via tracing
    • Guarantee: Scripts parsing stdout will receive only valid JSON
  2. Text Output Modes (default):

    • stdout: User-facing result summaries and human-readable reports only
    • stderr: All logs, diagnostics, and progress messages via tracing
    • Note: Text format content may evolve; use JSON modes for stable parsing
  3. Error Conditions:

    • Non-zero exit: stdout may be empty unless partial results are explicitly supported
    • All errors: Logged to stderr, never stdout

Examples

# JSON mode - stdout contains only JSON, logs go to stderr
deacon read-configuration --output json > config.json 2> logs.txt

# Text mode - stdout contains human-readable results, logs to stderr
deacon doctor > diagnosis.txt 2> logs.txt

# Parsing JSON output safely
OUTPUT=$(deacon read-configuration 2>/dev/null)
echo "$OUTPUT" | jq '.configFilePath'

Integration Guidelines

  • Automation/CI: Always use JSON output modes (--json, --output json) for reliable parsing
  • Human Use: Default text modes provide better readability and context
  • Logging: Use --log-level and --log-format to control stderr verbosity and format

Docker Integration

All Docker functionality is available when Docker is installed and running. If the Docker daemon isn’t available, deacon emits a clear runtime error with guidance to install or start Docker.

Workspace Trust

deacon up runs initializeCommand and any custom dotfiles install command on the host — outside the container sandbox. To prevent arbitrary host-side execution when cloning hostile repos, deacon gates these hooks behind a workspace-trust check that fails closed by default.

# Untrusted workspaces error out:
$ deacon up
Error: workspace is not trusted: /path/to/workspace
       Re-run with --trust-workspace (or --trust-workspace-persist to remember).

# Opt in for this run only:
deacon --trust-workspace up

# Opt in and remember (persisted to ~/.local/share/deacon/trusted_workspaces.json):
deacon --trust-workspace-persist up

# CI fail-closed mode — never auto-trust, error immediately on first untrusted run:
DEACON_NO_PROMPT=1 deacon up

The check only fires when host-side hooks are actually configured. Containers without initializeCommand or a host-side dotfiles install are unaffected. See SECURITY.md for the full threat model.

Usage

Reading DevContainer Configuration

The read-configuration command loads, processes, and outputs your devcontainer.json:

# In a directory with .devcontainer/devcontainer.json or .devcontainer.json
deacon read-configuration

# With explicit config path
deacon read-configuration --config /path/to/devcontainer.json

# Include merged configuration (with extends resolution)
deacon read-configuration --include-merged-configuration

# With debug logging
deacon read-configuration --log-level debug

Example output:

{
  "name": "my-dev-container",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "workspaceFolder": "/workspaces/my-project",
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  "customizations": {
    "vscode": {
      "extensions": ["ms-python.python"]
    }
  }
}

Variable Substitution: Variables like ${localWorkspaceFolder} are automatically replaced:

{
  "workspaceFolder": "${localWorkspaceFolder}/src",
  "containerEnv": {
    "PROJECT_ROOT": "${localWorkspaceFolder}"
  }
}

becomes:

{
  "workspaceFolder": "/home/user/project/src",
  "containerEnv": {
    "PROJECT_ROOT": "/home/user/project"
  }
}

Logging

Deacon uses the tracing ecosystem for structured logging.

  • Default log level: info
  • Default log format: text (human-readable)
  • CLI flag overrides: --log-level (error|warn|info|debug|trace), --log-format (text|json)
  • Environment overrides (take precedence before CLI flag processing sets RUST_LOG):
    • DEACON_LOG: Full filter specification (e.g. DEACON_LOG=deacon=debug,deacon_core=debug)
    • RUST_LOG: Standard Rust filter fallback if DEACON_LOG unset

When you specify --log-level, the CLI sets RUST_LOG internally to deacon=<level>,deacon_core=<level> prior to initializing the subscriber. Use DEACON_LOG for advanced per-module filtering; it will be honored as-is (and will emit a warning and fall back to info if invalid).

Guidance on levels:

  • info: High-level milestones and user‑visible state changes (container start/stop, template application summary, configuration load boundaries)
  • debug: Detailed decision points (config discovery paths, feature resolution steps, variable substitution reports)
  • trace: Very fine‑grained internals (iteration over collections, per-file copy decisions) – typically for deep troubleshooting only
  • warn: Recoverable issues or unexpected states deviating from normal expectations
  • error: Failures causing command termination or skipped critical workflow phases

Examples:

# Increase verbosity for troubleshooting configuration issues
deacon read-configuration --log-level debug

# Use JSON logs (machine parsing / CI ingestion)
deacon up --log-format json

# Advanced module filtering (show trace for config, keep others at info)
DEACON_LOG=deacon_core::config=trace,deacon=info deacon read-configuration

If you disable secret redaction (--no-redact), secret values may appear in logs—avoid in shared terminals or CI unless strictly necessary.

Development Build

cargo run -- --help
cargo test

Running Tests

The project supports both traditional cargo test and parallel execution via cargo-nextest.

Standard Test Commands (Serial Execution)

# Run all tests serially (default)
make test

# Fast feedback loop: unit + bins + examples + doctests (no integration)
make test-fast

# Development fast loop: fmt-check + clippy + fast tests
make dev-fast

Parallel Test Execution with cargo-nextest

For faster feedback, install cargo-nextest:

cargo install cargo-nextest --locked
# Or follow: https://nexte.st/book/pre-built-binaries.html

Then use the nextest targets:

# Fast parallel tests (excludes smoke/parity tests)
make test-nextest-fast

# Full test suite with parallel execution and test grouping
make test-nextest

# CI-aligned conservative profile
make test-nextest-ci

Test Groups: The project organizes tests into groups based on resource requirements:

  • docker-exclusive: Tests requiring exclusive Docker daemon access (serial)
  • docker-shared: Tests that can share Docker daemon (limited parallelism)
  • fs-heavy: Filesystem-intensive tests (limited parallelism)
  • unit-default: Fast unit tests with high parallelism
  • smoke: High-level integration tests (serial)
  • parity: Upstream CLI comparison tests (serial)

Timing data is automatically captured in artifacts/nextest/ for performance analysis.

Fallback Behavior: If cargo-nextest is not installed, the Make targets will fail with clear installation instructions. Always keep the standard make test working as a fallback.

Test Classification Checklist

When adding new tests, classify them into the appropriate group:

  1. Does the test use Docker?

    • No → unit-default or fs-heavy
    • Yes → Continue to step 2
  2. Does it require exclusive Docker daemon access?

    • Yes (manipulates daemon state) → docker-exclusive
    • No (just runs containers) → docker-shared
  3. Does it perform heavy filesystem operations?

    • Yes (large files, many I/O ops) → fs-heavy
  4. Is it an end-to-end integration test?

    • Yes (validates complete workflow) → smoke
    • Yes (compares with upstream CLI) → parity

Audit test assignments:

# List all tests with their group classifications
make test-nextest-audit

Example test naming for automatic classification:

// Docker-exclusive tests
#[test]
fn integration_lifecycle_full_up_down() { ... }

// Docker-shared tests  
#[test]
fn integration_build_with_cache() { ... }

// Smoke tests
#[test]
fn smoke_basic_workflow() { ... }

Troubleshooting Test Issues

Flaky tests in parallel execution:

  • Test passes with make test but fails with make test-nextest
  • Solution: Reclassify to more conservative group (e.g., docker-shareddocker-exclusive)
  • Update test name or .config/nextest.toml filter
  • Verify with make test-nextest-audit
  • Validate with multiple runs of make test-nextest

Tests requiring specific order:

  • Preferred: Refactor test to be independent
  • If unavoidable: Move to smoke group (serial execution)
  • Document the dependency in test comments

Slow tests:

  • Profile to identify bottleneck
  • Consider splitting into smaller tests
  • Move inherently slow end-to-end tests to smoke group
  • Use #[ignore] for very slow tests: cargo nextest run -- --ignored

For comprehensive troubleshooting, classification workflows, and remediation steps, see docs/testing/nextest.md.

Roadmap

Deacon focuses on consuming dev containers — building, running, and managing them — rather than authoring reusable features or publishing to registries. Coverage continues to expand across these specification domains:

  • Configuration resolution and parsing (devcontainer.json)
  • Feature consumption: installing and resolving community features during container builds
  • Template system for scaffolding new projects
  • Container lifecycle management
  • Docker/OCI integration
  • Cross-platform support

See the CLI specification for detailed architecture and planned features.

Binary Authenticity & Code Signing

Current release artifacts (tar.gz / zip) are not yet code signed. Integrity is provided via SHA256SUMS published with every release. You should always:

# Download archive and checksum listing
curl -LO https://github.com/get2knowio/deacon/releases/download/<version>/SHA256SUMS
grep '<archive-filename>' SHA256SUMS | sha256sum -c -

Planned enhancements (no tracking issue yet — file one if you need any of these prioritized):

  • GPG detached signature for SHA256SUMS (SHA256SUMS.asc)
  • macOS codesign + notarization
  • Windows Authenticode signature
  • Supply chain provenance (SLSA build attestation)

Until signatures are in place, rely on checksum verification and the GitHub release provenance. If you need reproducible build parity, the workflow captures the exact rustc -Vv used in each release (RUSTC_VERSION.txt asset). Deterministic builds for comparison can be performed via:

rustup toolchain install $(grep '^release:' RUSTC_VERSION.txt | cut -d':' -f2 | xargs)
cargo build --release --locked --all-features

If you have requirements around signed binaries and would like to help accelerate this, comment on the tracking issue once it is opened.

Contributing

See CONTRIBUTING.md for development workflow, testing guidelines, and contribution requirements.

Continuous Integration

CI runs via GitHub Actions and uses the Makefile + cargo-nextest:

  • Lint: rustfmt check, cargo check, clippy, and doctests
  • Test (Ubuntu): make test-nextest-fast with Docker available
  • Smoke (Ubuntu): make test-smoke (serial, Docker required)
  • Nextest CI (Ubuntu/macOS): make test-nextest-ci with timing artifact artifacts/nextest/ci-timing.json
  • Other OS (macOS/Windows): runs unit + non‑smoke integration tests and separate smoke tests; macOS uses Colima for Docker

Notes:

  • Networked integration tests run only in selected jobs (smoke and nextest-ci) via DEACON_NETWORK_TESTS=1 to keep the fast test job hermetic.
  • Test grouping and concurrency are configured in .config/nextest.toml. See docs/testing/nextest.md for details.

Test Coverage

We use cargo-llvm-cov (LLVM source-based coverage) locally and in CI.

  • Install toolchain addon and helper:

    • rustup component add llvm-tools-preview
    • cargo install cargo-llvm-cov
  • Run coverage locally and open HTML report:

    • cargo llvm-cov --workspace --open
  • Generate LCOV for external services:

    • cargo llvm-cov --workspace --lcov --output-path lcov.info

CI enforces a minimum line coverage threshold (see MIN_COVERAGE in .github/workflows/ci.yml). To try the same locally:

  • cargo llvm-cov --workspace --fail-under-lines 80

Coverage reporting is published to Coveralls for the main branch and PRs: https://coveralls.io/github/get2knowio/deacon

About

Rust implementation of the Development Containers CLI

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages