Quick start · Use cases · Plan & apply · How it works · Docs · Roadmap
Containust (ctst) is a daemon-less container runtime written in Rust. You
describe a stack in a small declarative .ctst file, ctst plan shows exactly
what will change, and ctst run applies it. There is no background service and
no root daemon socket. Every command reads and writes plain files in your
project, so state survives reboots, works offline, and can be copied to another
machine.
It runs natively on Linux with user + PID namespaces and cgroups v2, and in a small QEMU micro-VM on macOS and Windows.
|
Each |
|
|
|
User + PID namespaces are on by default, all capabilities are dropped,
|
|
A Rust SDK exposes the parser, dependency graph, planner and runtime, so you can drive containers from your own product without shelling out. |
Air-gapped and sovereign sites: defense, government, industrial OT, regulated labs
Pull and verify images on a connected machine, carry the content-addressed store across the air gap, and run with the network policy enforced.
# connected side
ctst download stack.ctst --image postgres:16 # images, presets, VM assets if needed
# copy .containust/images and .containust/layers across the gap, then:
ctst --offline plan stack.ctst # remote references fail closed
ctst --offline run -d stack.ctstLocal dev stacks without Docker Desktop: no daemon, no licensing surprises, one binary
Describe the API, database and cache once. Components on the same network
reach each other by name, and CONNECT injects *_HOST / *_PORT env vars.
On macOS and Windows the same file runs in a QEMU micro-VM with port
forwarding.
COMPONENT db { image = "image://postgres@sha256:…" port = 5432 network = "app" }
COMPONENT api { image = "file:///work/api/rootfs" port = 8080 network = "app" memory = "256MiB" }
CONNECT api -> db // api reaches db at db:5432
EXPOSE 8080 // http://127.0.0.1:8080 on the host
Already on Compose? Run ctst convert docker-compose.yml -o stack.ctst.
CI environments and drift detection: fail the build when reality diverges from the file
ctst plan --detailed-exitcode stack.ctst # exit 2 if anything would change
ctst plan --json | jq '.changes[] | select(.action != "keep")'Ephemeral test stacks start without Docker-in-Docker or a privileged socket mount.
Edge boxes, appliances, kiosks: small footprint, nothing idling in the background
One static binary plus files on disk. Restart policies and healthchecks are
enforced by reconciliation: every ctst ps or ctst run pass restarts failed
containers and probes health. A systemd timer gives you continuous supervision
without a resident daemon.
# /etc/systemd/system/ctst-reconcile.service
[Service]
Type=oneshot
WorkingDirectory=/srv/app
ExecStart=/usr/local/bin/ctst ps
# /etc/systemd/system/ctst-reconcile.timer (systemctl enable --now ctst-reconcile.timer)
[Timer]
OnBootSec=30s
OnUnitActiveSec=30s
[Install]
WantedBy=timers.targetReproducible demos, workshops and labs: same digests, same result, any laptop
Ship a .ctst file with pinned digests and a pre-filled image store. Attendees
run ctst run with no registry account, no network, and no daemon setup.
Embedding containers in a Rust product: SDK, not a subprocess
use containust_sdk::deploy::{ApplyOptions, Engine, EngineOptions};
let file = std::path::Path::new("stack.ctst");
let data_dir = containust_common::constants::project_dir(file);
let engine = Engine::with_options(EngineOptions {
state_file: data_dir.join("state").join("state.json"),
data_dir,
offline: false,
});
println!("{}", engine.plan(file)?.summary());
let outcome = engine.apply(file, ApplyOptions { prune: true })?;See docs/SDK_GUIDE.md.
VERSION=1.5.0
TARGET=x86_64-unknown-linux-gnu # or aarch64-unknown-linux-gnu, *-apple-darwin, x86_64-pc-windows-msvc
curl -LO "https://github.com/RemiPelloux/Containust/releases/download/v${VERSION}/ctst-${TARGET}.tar.gz"
curl -LO "https://github.com/RemiPelloux/Containust/releases/download/v${VERSION}/ctst-${TARGET}.tar.gz.sha256"
sha256sum -c "ctst-${TARGET}.tar.gz.sha256"
tar xzf "ctst-${TARGET}.tar.gz" && sudo install -m 755 ctst /usr/local/bin/ctstOther install paths: .deb / .rpm, Homebrew, winget, from source
| Channel | Command |
|---|---|
| Debian / Ubuntu | sudo dpkg -i ctst_1.5.0_amd64.deb |
| Fedora / RHEL | sudo rpm -i ctst-1.5.0-1.amd64.rpm |
| Homebrew | brew install --formula ./Formula/ctst.rb |
| winget | manifest in packaging/winget/ |
| Source | cargo install --path crates/containust-cli |
Releases ship a cosign-signed SHA256SUMS. See docs/PACKAGING.md
and docs/RUNBOOKS.md for verification. macOS and Windows need
QEMU 7+. Run ctst doctor to check your host.
A 60-second tour:
ctst doctor # check backend, cgroups, cache, QEMU
ctst build examples/alpine_preset.ctst # import preset://alpine into the project store
ctst run examples/alpine_preset.ctst # prints "hello from preset://alpine"Then, with your own long-running stack:
ctst pull postgres:16 # OCI pull into the local catalog
ctst plan stack.ctst # what would change?
ctst run -d stack.ctst # apply it
ctst ps --all # reconcile + list (add --tui for a live dashboard)
ctst logs api --follow
ctst exec api uname -a
ctst run -d stack.ctst # re-run: everything is "keep", nothing restarts
ctst stop && ctst rm api dbNew in 1.3.0: ctst run is declarative. Every component is fingerprinted
(image, command, env including CONNECT wiring, limits, volumes, ports,
network, restart policy, healthcheck, namespace policy), and the fingerprint
of what was last applied is stored in .containust/state/applied.json.
| Marker | Action | When | ctst run does |
|---|---|---|---|
+ |
create | no container with that name | create + start |
~ |
replace | spec changed, or an upstream it CONNECTs to is being replaced |
stop → remove → create → start, one at a time in dependency order |
> |
start | same spec, container stopped/failed | start |
= |
keep | same spec, running | nothing |
- |
remove | no longer declared | stop + remove, only with --prune |
$ ctst plan stack.ctst
Plan for stack.ctst
────────────────────────────────────────────────
~ replace db (spec changed)
image: tar:///opt/images/pg16.tar
~ replace api (upstream 'db' replaced)
= keep cache
- remove legacy (no longer declared)
Plan: 0 to create, 2 to replace, 0 to start, 1 unchanged, 1 to remove.
Removals apply only with `ctst run --prune`.
The ledger is written after each component. If an apply fails part-way, the next run resumes from where it stopped.
New in 1.4.0: zero-downtime updates. Give a component
update = "start-first" and a healthcheck, and ~ replace stops being a
stop-then-start:
~ replace api (spec changed) [start-first]
Creating container 'api~next'... new version starts beside the old one
Waiting for the new 'api' to become healthy...
Traffic moved to the new 'api'; draining the old one for 5s...
old one stops, api~next becomes api
The published port never closes: the forwarder switches its target in one
atomic step, so new connections reach the new container while open ones
finish on the old one. If the new version never becomes healthy it is
thrown away and the old one keeps serving, and a run that gets interrupted
is finished (or cleanly undone) by the next ctst run. A CI gate replaces a
live HTTP service under load and requires zero failed requests.
Start-first works on Linux for services published through the forwarder
(network = "none" or an EXPOSE remap) that are not part of a CONNECT;
everything else falls back to recreate, and ctst plan says why. Details in
CTST_LANG.md §13.
- Parse & resolve. A
nomgrammar readsCOMPONENT,CONNECTandEXPOSE;IMPORTed files andFROMchains are merged into each component. Unsupported properties, unreachableCONNECTs and remote images under--offlineare reported before anything runs. - Graph.
petgraphtopologically sortsCONNECTedges. Cycles are rejected, and upstreams start first. - Plan. Desired fingerprints are diffed against live containers and the ledger.
- Apply. On Linux, a pipe-synced fork/exec enters user, PID, mount, net, IPC and UTS namespaces, applies cgroups v2 limits, drops capabilities, and pivots into the rootfs. On macOS and Windows, the same requests go over a versioned JSON RPC to an agent inside a pinned Alpine QEMU VM.
The crates are layered common → core → image → compose → runtime → sdk → cli.
See ARCHITECTURE.md.
| Containust | Docker | Podman | |
|---|---|---|---|
| Background daemon | none | dockerd (root) |
none |
| Stack definition | .ctst (typed, validated) |
Compose YAML | Compose / Kube YAML |
| Diff before apply | ctst plan |
— | — |
| Zero-downtime replace | update = "start-first" (health-gated) |
swarm services only | — |
| Enforced offline mode | --offline, fail-closed |
— | — |
| Image store | content-addressed, per project, portable | global | global |
| Registry ecosystem | Hub + GHCR pull | full | full |
| Build from Dockerfile | — (import rootfs / tar / OCI) | ✓ | ✓ |
| Orchestration / swarm | — | swarm | pods |
Containust doesn't try to replace Docker everywhere. It targets places where a daemon, a registry dependency, or opaque state is a liability.
| Command | Purpose |
|---|---|
ctst plan [--json] [--detailed-exitcode] |
Diff desired vs running, read-only |
ctst run [-d] [--prune] |
Apply the plan |
ctst build [--dry-run] |
Import images into the project catalog |
ctst pull [--require-provenance] |
OCI pull (Hub, GHCR) into the catalog |
ctst download [--image] [--all-presets] [--vm] |
Prefetch everything for offline / on-prem use |
ctst ps [--all] [--tui] |
Reconcile, enforce restart and health, list |
ctst logs [-f] · ctst exec |
Inspect |
ctst stop [--force] · ctst rm |
Tear down |
ctst images · ctst convert · ctst doctor · ctst vm start/stop |
Catalog, Compose → .ctst, diagnostics, VM lifecycle |
Global flags: --offline, --state-file. Full reference:
docs/CLI_REFERENCE.md.
| Host | Backend | Needs |
|---|---|---|
| Linux 5.10+ (x86_64, aarch64) | Native namespaces + cgroups v2 | Root or delegated userns |
| macOS (Intel, Apple silicon) | QEMU micro-VM + agent | QEMU 7+, first-run VM assets |
| Windows x86_64 | QEMU micro-VM + agent | QEMU 7+, first-run VM assets |
CI enforces performance budgets on parse/resolve, image import, 10k-file
extraction and stop latency. Graceful stop returns as soon as the process
exits instead of after a fixed 2 s wait. ctst pull reuses layers already in
the store. See docs/PERFORMANCE.md.
| site/docs/ | HTML documentation (start here) |
| docs/HowToUse.md | Operator guide: install, first run, ports, offline, troubleshooting |
| docs/CTST_LANG.md | .ctst language reference |
| docs/CLI_REFERENCE.md | Every command and flag |
| docs/MIGRATION_FROM_DOCKER.md | Coming from Docker / Compose |
| docs/THREAT_MODEL.md | Security model |
| docs/README.md | Full documentation map |
cargo test --workspace --lib --tests
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo deny check
python3 scripts/gen_readme_assets.py # regenerate the animated README SVGsSee docs/CONTRIBUTING.md.
Containust is proprietary and source-available under the Containust Commercial License. Evaluation use is allowed. Production use, redistribution, and commercial embedding require a paid license. See COMMERCIAL.md.