Welcome, and thank you for your interest in contributing to Containust — a daemon-less, sovereign container runtime written in Rust.
This guide covers everything you need to go from zero to merged pull request. We value code that is clear, modular, secure, and well-tested. Every contribution, no matter how small, makes Containust better.
| Tool | Minimum Version | Purpose |
|---|---|---|
| Rust | 1.85+ (Edition 2024, stable channel) | Build and test |
| Git | 2.x | Version control |
| Linux | Kernel 5.10+ | Native runtime (namespaces, cgroups v2, OverlayFS) |
| macOS | 13+ | Development + runtime via VM backend |
| Windows | 10+ | Development + runtime via VM backend |
| QEMU | 7.0+ (macOS/Windows) | Required for VM backend on non-Linux platforms |
| cargo-deny | latest | Dependency auditing |
Linux — no additional dependencies needed. The native backend uses direct syscalls.
macOS:
brew install qemuQEMU provides the VM backend for running Linux containers. Hardware acceleration via Apple HVF is used automatically.
Windows:
winget install QEMU.QEMUQEMU provides the VM backend. Hyper-V/WHPX acceleration is used when available.
git clone https://github.com/RemiPelloux/Containust.git
cd Containust
cargo build --workspaceThe workspace compiles on all three platforms. Platform-specific code is gated with #[cfg(target_os = "...")].
cargo test --workspaceThe project enforces a zero warnings policy with clippy::pedantic and clippy::nursery:
cargo clippy --workspace -- -D warnings
cargo fmt --workspace --check
cargo deny checkAll three commands must pass before submitting a pull request.
┌──────────────┐
│ containust- │
│ cli │ ← binary: ctst
└──────┬───────┘
│
┌──────┴───────┐
│ containust- │
│ tui │
└──────┬───────┘
│
┌──────┴───────┐
│ containust- │
│ sdk │ ← public API facade
└──┬───┬───┬──┘
│ │ │
┌────────────┘ │ └────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ containust-│ │ containust-│ │ containust-│
│ compose │ │ runtime │ │ image │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
│ ┌─────┴──────┐ │
│ │ containust-│ │
│ │ ebpf │ │
│ └─────┬──────┘ │
│ │ │
└────────┬───────┴────────┬────────┘
▼ ▼
┌────────────┐ (also depends on)
│ containust-│
│ core │
└─────┬──────┘
│
▼
┌────────────┐
│ containust-│
│ common │ ← leaf crate, zero internal deps
└────────────┘
| Crate | Layer | Responsibility |
|---|---|---|
containust-common |
Common | Shared types, error definitions, configuration models, constants |
containust-core |
Core | Linux namespace, cgroup v2, OverlayFS, and capability primitives |
containust-image |
Engine | Image/layer storage, source protocols (file://, tar://, https://), SHA-256 validation |
containust-runtime |
Engine | Container lifecycle state machine, process spawning, metrics collection |
containust-compose |
Engine | .ctst parser (nom), dependency graph (petgraph), auto-wiring logic |
containust-ebpf |
Observe | eBPF-based syscall, file access, and network monitoring (aya) |
containust-sdk |
SDK | Public facade: ContainerBuilder, GraphResolver, EventListener |
containust-tui |
CLI | Interactive terminal dashboard (ratatui) |
containust-cli |
CLI | ctst binary with all subcommands (clap) |
Dependencies flow strictly downward. A crate may only depend on crates in its own layer or lower layers. Cross-layer or upward dependencies are forbidden. See architecture.mdc for the full dependency matrix.
| Element | Convention | Example |
|---|---|---|
| Modules and files | snake_case |
pivot_root.rs, file_monitor.rs |
| Types (structs, enums, traits) | PascalCase |
ContainerState, LayerHash |
| Functions and methods | snake_case |
create_namespace, resolve_graph |
| Constants | SCREAMING_SNAKE_CASE |
DEFAULT_CGROUP_PATH |
| Type parameters | Single uppercase or short PascalCase |
T, Err |
- Single responsibility — each function does one thing.
- Max 25 lines per function body. Extract helpers if longer.
- Max 3 parameters — beyond 3, use a config/options struct.
- Early returns — use guard clauses to reduce nesting.
- Prefer pure functions — isolate side effects (I/O, syscalls) at module boundaries.
- Return
Result<T, E>for all fallible operations.
- Single concern per file —
storage.rshandles storage, not networking. - Max 300 lines per file — split into submodules when approaching this limit.
- Max 10 public items per module — a module exposing more is doing too much.
- No catch-all modules —
utils.rs,helpers.rs, andmisc.rsare banned.
- No
.unwrap()in library crates — use.expect("reason")only in tests. - No
panic!in library code — reserve panics for truly unrecoverable states. - Library crates use
thiserror— each crate defines its own error enum. - The CLI crate uses
anyhow— for ergonomic error propagation to the user. - Error messages must be actionable — bad:
"failed"; good:"failed to mount overlayfs at {path}: {source}".
- Every
unsafeblock requires a// SAFETY:comment explaining invariant upholding. - Minimize unsafe scope — wrap in safe abstractions as close to the call site as possible.
- No
unsafein public APIs — encapsulate in safe wrappers that validate preconditions.
- Read-only rootfs by default — only declared volumes are writable.
- Drop all Linux capabilities — allowlist only what is required, with justification comments.
- No secrets in state files or logs — scrub sensitive data from all output.
- Validate all external input — file paths, image URIs, and
.ctstfiles are checked before use.
-
Define the subcommand in
crates/containust-cli/src/commands/mod.rsby adding a new variant to theCommandsenum (clap derive). -
Create the handler module at
crates/containust-cli/src/commands/<name>.rs. The handler should accept parsed arguments and call intocontainust-sdk. -
Wire the handler into the
matchblock incommands/mod.rsthat dispatches on theCommandsenum. -
Add tests:
- Unit test in the handler module (
#[cfg(test)] mod tests { ... }). - Integration test in
tests/integration/exercising the command end-to-end.
- Unit test in the handler module (
-
Update documentation — add the command to
docs/CLI_REFERENCE.mdandREADME.md.
-
Add the keyword token to the lexer/tokenizer in
crates/containust-compose/src/parser/. Update the reserved keywords list. -
Update the AST types in
crates/containust-compose/src/ast.rsto represent the new construct. -
Implement the parser rule using nom combinators. Follow the existing parsing patterns for consistency.
-
Add semantic analysis — validate the new keyword's constraints (type checks, reference resolution) in the analysis pass.
-
Add tests:
- Parser unit test: valid syntax parses correctly.
- Parser unit test: invalid syntax produces
E001or appropriate error. - Semantic test: constraints are enforced (references exist, types match).
-
Update documentation — add the keyword to
docs/CTST_LANG.mdand the reserved keywords table.
-
Define the trait (if not already present) in
crates/containust-core/src/. The trait should abstract namespace creation, filesystem setup, and process spawning. -
Implement the trait in a new module within
containust-core. Each method should returnResult<T, E>with descriptive errors. -
Add platform detection — use
#[cfg(target_os = "...")]or runtime feature detection to select the appropriate backend. -
Register the backend in the engine initialization code so that
containust-runtimecan discover and use it. -
Add tests:
- Unit tests with mocked system calls.
- Integration test in
tests/integration/using the real backend (Linux only).
-
Gate behind a feature flag if the backend introduces heavy dependencies (e.g., eBPF, FUSE).
The VM backend (crates/containust-runtime/src/backend/vm.rs) communicates with a lightweight JSON-RPC agent running inside a QEMU Alpine Linux VM. When working on the VM backend:
-
Build the VM agent — The agent is a minimal Rust binary that exposes the
ContainerBackendtrait methods over JSON-RPC/TCP. -
Test locally — On macOS, ensure QEMU is installed (
brew install qemu). Runctst vm startto boot the VM, then run container tests against it. -
Platform detection — The
detect_backend()function inbackend/mod.rsselectsLinuxNativeBackendon Linux andVMBackendon all other platforms. Use#[cfg(target_os)]guards for platform-specific code. -
The
ContainerBackendtrait — All backend implementations must implement every method in theContainerBackendtrait (defined inbackend/mod.rs):create,start,stop,exec,remove,logs,list, andis_available.
The CI pipeline runs on three OS targets to validate cross-platform compatibility:
| OS | Runner | Backend Tested | Notes |
|---|---|---|---|
| Linux | ubuntu-latest |
LinuxNativeBackend |
Full integration tests including namespace/cgroup operations |
| macOS | macos-latest |
Compilation + unit tests | VM backend integration tests require QEMU in CI |
| Windows | windows-latest |
Compilation + unit tests | VM backend integration tests require QEMU in CI |
Unit tests run on all three platforms. Integration tests that require Linux kernel primitives are gated with #[cfg(target_os = "linux")] or are skipped on non-Linux runners.
- Co-located with source code inside
#[cfg(test)] mod tests { ... }. - One assertion per test when feasible.
- Must be deterministic — no flaky tests. Mock external dependencies.
- Must complete in < 100ms individually.
- Located in
tests/integration/. - May touch real namespaces, cgroups, and filesystems.
- Must complete in < 5 seconds individually.
- Use
tempfilefor filesystem operations — never write to fixed paths.
- End-to-end tests exercising
ctstsubcommands with real.ctstfiles. - Located in
tests/integration/alongside other integration tests. - Validate the full pipeline: parse → build → run → verify → stop.
- >=90% line coverage for library crates.
- CLI/TUI crates may have lower coverage due to I/O-heavy code.
#[test]
fn <unit>_<scenario>_<expected_outcome>()
Examples:
fn container_with_invalid_image_returns_error()fn parser_empty_input_produces_empty_ast()fn cgroup_memory_limit_enforced_correctly()
Runs on every push and pull request to main:
| Job | Command | Purpose |
|---|---|---|
| Check | cargo check --workspace |
Verify the workspace compiles |
| Format | cargo fmt --workspace --check |
Enforce consistent formatting |
| Clippy | cargo clippy --workspace -- -D warnings |
Zero-warning lint policy |
| Test | cargo test --workspace |
Run all unit and integration tests |
Runs on push, pull request, and weekly schedule (Monday 06:00 UTC):
| Job | Command | Purpose |
|---|---|---|
| Audit | cargo deny check |
Check for known vulnerabilities and license compliance |
- All CI jobs must pass before merge.
- At least one approving review is required.
- Force pushes to
mainare disabled.
| Prefix | Purpose | Example |
|---|---|---|
feat/ |
New feature | feat/healthcheck-support |
fix/ |
Bug fix | fix/cgroup-path-resolution |
docs/ |
Documentation only | docs/sdk-guide-examples |
refactor/ |
Code restructuring (no behavior change) | refactor/parser-combinator-cleanup |
test/ |
Test additions or fixes | test/namespace-edge-cases |
chore/ |
Tooling, CI, dependencies | chore/update-clap-to-4.5 |
<type>(<scope>): <short summary>
<optional body explaining why, not what>
Examples:
feat(compose): add HEALTHCHECK keyword to .ctst parserfix(runtime): handle cgroup v2 delegation correctly on systemd hostsdocs(sdk): add ContainerBuilder usage examples
Before requesting review, ensure:
-
cargo check --workspacepasses -
cargo clippy --workspace -- -D warningspasses -
cargo fmt --workspace --checkpasses -
cargo test --workspacepasses -
cargo deny checkpasses - New code has unit tests (>=90% coverage for library crates)
- Documentation is updated if public API changed
Reviewers evaluate contributions on:
- Correctness — does the code do what it claims?
- Architecture — does it respect the layer DAG and crate boundaries?
- Modularity — functions <=25 lines, files <=300 lines, no god-objects?
- Security — no
.unwrap()in libraries, unsafe blocks justified, inputs validated? - Tests — meaningful coverage, deterministic, fast?
- Documentation — public items have doc comments, error messages are actionable?
Containust follows Semantic Versioning:
- MAJOR — breaking changes to the SDK public API or
.ctstlanguage syntax. - MINOR — new features, new CLI commands, new
.ctstkeywords (backward-compatible). - PATCH — bug fixes, performance improvements, documentation updates.
Maintain CHANGELOG.md in Keep a Changelog format:
## [Unreleased]
### Added
- HEALTHCHECK support in .ctst language (#42)
### Fixed
- Cgroup path resolution on systemd-managed hosts (#38)
### Changed
- Upgraded petgraph from 0.6 to 0.7 (#41)# Update version in all Cargo.toml files
cargo set-version --workspace 0.2.0
# Update CHANGELOG.md — move [Unreleased] to [0.2.0]
# Commit and tag
git add -A
git commit -m "release: v0.2.0"
git tag v0.2.0
# Push
git push origin main --tags- Issues: Open an issue on GitHub for bugs, feature requests, or questions.
- Architecture: Read ARCHITECTURE.md for design rationale.
- Language spec: Read CTST_LANG.md for the
.ctstformat reference. - Error codes: Read ERRORS.md for the full error catalog.
- Cursor rules: Browse
.cursor/rules/for enforced coding standards.
Built with Rust. Designed for sovereignty.