Binary: ctst
Version: 0.1.0
Platform: Linux (native), macOS (VM backend), Windows (VM backend)
ctst [OPTIONS] <COMMAND> [ARGS...]
Containust is a daemon-less, sovereign container runtime written in Rust. The ctst binary is the single entry point for building, deploying, inspecting, and managing containers defined in .ctst composition files. On Linux, it communicates with the kernel directly via syscalls. On macOS and Windows, it uses a lightweight QEMU VM with a JSON-RPC agent — no background daemon required on any platform.
git clone https://github.com/RemiPelloux/Containust.git
cd Containust
cargo install --path crates/containust-clictst --version
# ctst 0.1.0These flags apply to every subcommand.
| Flag | Description | Default | Env Override |
|---|---|---|---|
--offline |
Block all outbound network access during build and run | false |
CONTAINUST_OFFLINE=1 |
--state-file <PATH> |
Path to the state index file | .containust/state.json (project-local) |
CONTAINUST_STATE_FILE |
--help |
Print help information and exit | — | — |
--version |
Print version information and exit | — | — |
Tracing verbosity is controlled by the CONTAINUST_LOG environment variable, which accepts
tracing filter directives
(e.g., info, containust_runtime=debug).
Parse a .ctst composition file, resolve imports, download or locate images, and assemble filesystem layers.
ctst build [OPTIONS] [FILE]
| Argument | Description | Default |
|---|---|---|
FILE |
Path to the .ctst composition file |
containust.ctst |
Inherits all global options.
ctst build performs the following steps:
- Parse the
.ctstfile using the nom-based parser. - Resolve imports — recursively load any
IMPORTdirectives. - Fetch images — download or locate images from
file://,tar://, or remote sources. Validates every layer with SHA-256. - Assemble layers — build OverlayFS layer stacks and store them in the image store.
- Analyze binaries — run distroless dependency analysis when enabled.
Built layers are cached. Subsequent builds skip layers whose content hash has not changed.
$ ctst build webapp.ctst
⠋ Parsing webapp.ctst...
✓ Parsed 3 components, 2 connections
⠋ Resolving images...
[1/3] api file:///opt/images/myapp sha256:a1b2c3d4... 42.8 MiB
[2/3] db tar:///backups/pg15.tar sha256:e5f6a7b8... 89.1 MiB
[3/3] cache file:///opt/images/redis sha256:c9d0e1f2... 12.3 MiB
✓ All images resolved (144.2 MiB total)
⠋ Building layers...
Layer sha256:a1b2c3d4 → 3 sub-layers 42.8 MiB (cached)
Layer sha256:e5f6a7b8 → 5 sub-layers 89.1 MiB (built in 1.4s)
Layer sha256:c9d0e1f2 → 2 sub-layers 12.3 MiB (cached)
✓ Build complete (1.4s)
Layers are stored under .containust/images/ (project-local) keyed by their SHA-256 content hash. A layer is rebuilt only when its source content changes. Use --offline to restrict builds to locally cached layers only.
| Code | Meaning |
|---|---|
0 |
Build succeeded |
1 |
Build failed (parse error, missing image, hash mismatch) |
2 |
Invalid arguments |
4 |
Source file or image not found |
# Build from the default containust.ctst in the current directory
ctst build
# Build from a specific file
ctst build infrastructure/production.ctst
# Build in offline mode (no network access — cached layers only)
ctst build --offline
# Build with a custom state file location
ctst build --state-file /tmp/dev-state.json webapp.ctstDisplay the planned infrastructure changes without applying them (dry run).
ctst plan [OPTIONS] [FILE]
| Argument | Description | Default |
|---|---|---|
FILE |
Path to the .ctst composition file |
containust.ctst |
Inherits all global options.
ctst plan compares the desired state described in the .ctst file against the current state recorded in the state file. It outputs a diff-style summary showing what would change if ctst run were executed.
No containers are created, started, or stopped.
The output uses diff-style markers:
| Marker | Meaning |
|---|---|
+ |
New component to be created |
~ |
Existing component to be modified |
- |
Running component to be removed |
$ ctst plan production.ctst
Containust Plan — production.ctst
══════════════════════════════════
+ api image=file:///opt/images/myapp port=8080 mem=256MB
+ db image=tar:///backups/pg15.tar port=5432 mem=512MB
~ cache image=file:///opt/images/redis mem: 64MB → 128MB
- legacy-svc (will be stopped and removed)
Plan: 2 to add, 1 to change, 1 to destroy.
| Code | Meaning |
|---|---|
0 |
Plan computed — changes detected or no changes |
1 |
Failed to compute plan (parse error, corrupt state) |
2 |
Invalid arguments |
4 |
Composition file not found |
# Plan using the default file
ctst plan
# Plan a specific composition
ctst plan staging.ctst
# Plan in offline mode
ctst plan --offline production.ctst
# Plan with verbose tracing
CONTAINUST_LOG=debug ctst planDeploy the component graph defined in a .ctst composition file.
ctst run [OPTIONS] [FILE]
| Argument / Flag | Description | Default |
|---|---|---|
FILE |
Path to the .ctst composition file |
containust.ctst |
-d, --detach |
Run containers in the background and return immediately | false |
Inherits all global options.
ctst run performs a full deployment:
- Parse and resolve the
.ctstfile (equivalent toctst buildif images are not yet built). - Compute the dependency graph from
CONNECTdirectives using petgraph. - Topological sort to determine startup order. Components with no dependencies start in parallel.
- Create containers — set up namespaces (PID, mount, network, IPC, UTS), cgroups v2 resource limits, and OverlayFS mounts.
- Start processes in the correct order, injecting connection environment variables from
CONNECTwiring. - Update the state file with container metadata.
Containers are started in topological order derived from CONNECT directives. Independent components (no inbound or outbound connections) start in parallel. A component only starts after all of its dependencies report a healthy state.
When -d / --detach is passed, ctst run daemonizes the container processes and returns immediately. The state file is updated and containers continue running in the background. Use ctst ps to monitor and ctst stop to shut down.
Without --detach, ctst run remains in the foreground, streaming logs to stdout. Press Ctrl+C to initiate graceful shutdown.
$ ctst run -d production.ctst
Containust Deploy — production.ctst
════════════════════════════════════
✓ db started pid=48201 port=5432 mem_limit=512MB
✓ cache started pid=48215 port=6379 mem_limit=128MB
✓ api started pid=48230 port=8080 mem_limit=256MB
All 3 containers running (detached).
State saved to .containust/state.json
| Code | Meaning |
|---|---|
0 |
All containers started successfully |
1 |
One or more containers failed to start |
2 |
Invalid arguments |
3 |
Permission denied (insufficient privileges for namespace/cgroup operations) |
4 |
Composition file or image not found |
# Run the default composition in the foreground
ctst run
# Run in detached mode
ctst run -d
# Run a specific file, detached, in offline mode
ctst run -d --offline production.ctst
# Run with debug logging
CONTAINUST_LOG=debug ctst run
# Run with a custom state file
ctst run --state-file /tmp/dev-state.json dev.ctstList containers with their status and resource metrics.
ctst ps [OPTIONS]
| Flag | Description | Default |
|---|---|---|
-a, --all |
Show all containers including stopped and failed | false |
--tui |
Launch the interactive TUI dashboard | false |
Inherits all global options.
ctst ps reads the state file and queries cgroups v2 for live resource metrics. By default it shows only running containers.
| Column | Description | Example |
|---|---|---|
CONTAINER ID |
Truncated UUID (first 12 characters) | a1b2c3d4e5f6 |
NAME |
Component name from the .ctst file |
api |
STATE |
Current lifecycle state | running |
CPU% |
CPU usage percentage from cgroup stats | 2.3% |
MEM USAGE |
Current memory consumption | 45.2 MiB |
NET I/O |
Network bytes received / transmitted | 1.2 MiB / 340 KiB |
UPTIME |
Time since container started | 2h 14m |
| State | Description |
|---|---|
created |
Container exists but process has not started |
running |
Process is active |
stopped |
Process exited normally (exit code 0) |
failed |
Process exited with a non-zero exit code |
CPU and memory values are read from the cgroup v2 unified hierarchy (/sys/fs/cgroup). Memory is displayed in human-readable units (B, KiB, MiB, GiB). CPU percentage is computed over a sampling window.
Network I/O is read from the container's network namespace counters.
When launched with --tui, an interactive terminal dashboard (powered by ratatui) takes over the terminal.
Navigation keys:
| Key | Action |
|---|---|
q / Esc |
Quit the dashboard |
Tab |
Switch between panels (containers, metrics, logs) |
↑ / ↓ |
Scroll through container list |
← / → |
Cycle metric time ranges |
/ |
Open search / filter |
Enter |
Expand selected container details |
| Code | Meaning |
|---|---|
0 |
Listing succeeded |
1 |
Failed to read state file or query metrics |
4 |
State file not found |
# List running containers
ctst ps
# List all containers, including stopped
ctst ps --all
# Launch the interactive TUI dashboard
ctst ps --tui
# Use a custom state file
ctst ps --state-file /tmp/dev-state.json --allExecute a command inside a running container by joining its Linux namespaces.
ctst exec <CONTAINER> -- <COMMAND...>
| Argument | Description | Required |
|---|---|---|
CONTAINER |
Container ID (or prefix) or component name | Yes |
COMMAND... |
Command and arguments to execute inside the container | Yes |
Inherits all global options.
ctst exec joins the target container's Linux namespaces — PID, mount, network, IPC, and UTS — then executes the specified command within that isolated environment. The process sees the container's filesystem, network stack, and process tree.
- Lookup — Resolve the container by ID or name from the state file.
- Open namespace file descriptors — Read
/proc/<pid>/ns/{pid,mnt,net,ipc,uts}for the container's init process. setns()syscall — Join each namespace.chroot/pivot_root— Enter the container's root filesystem.execvp— Replace the current process with the requested command.
If the command is an interactive shell (e.g., /bin/sh, /bin/bash), ctst exec allocates a pseudo-TTY and attaches stdin/stdout/stderr for interactive use. Non-interactive commands run, print their output, and exit.
ctst exec forwards the exit code of the executed command. Additional codes:
| Code | Meaning |
|---|---|
0 |
Command succeeded |
1 |
General error (container not running, namespace join failed) |
4 |
Container not found |
126 |
Command found but cannot be executed (permission denied inside container) |
127 |
Command not found inside the container |
# Open an interactive shell
ctst exec api -- /bin/sh
# Run a one-off database query
ctst exec db -- psql -U postgres -c "SELECT version();"
# Check the filesystem inside a container
ctst exec cache -- ls -la /data
# Inspect environment variables
ctst exec api -- env
# Use a container ID prefix instead of name
ctst exec a1b2c3 -- cat /etc/hostnameView logs for a container.
ctst logs [OPTIONS] <CONTAINER>
| Argument | Description | Required |
|---|---|---|
CONTAINER |
Container ID (or prefix) or component name | Yes |
| Flag | Description | Default |
|---|---|---|
-f, --follow |
Follow log output in real time (stream new lines as they are written) | false |
Inherits all global options.
ctst logs retrieves and displays the stdout/stderr output captured from a container's main process. Logs are stored as append-only files on disk and persist across container restarts.
When --follow is specified, ctst logs tails the log file and streams new output to your terminal until interrupted with Ctrl+C.
$ ctst logs api
2026-02-24T10:30:01Z [INFO] Server started on 0.0.0.0:8080
2026-02-24T10:30:02Z [INFO] Connected to database at db:5432
2026-02-24T10:31:15Z [INFO] Handled 42 requests
| Code | Meaning |
|---|---|
0 |
Logs retrieved successfully |
1 |
Failed to read logs |
4 |
Container not found |
# View logs for a container by name
ctst logs api
# Follow logs in real time
ctst logs --follow api
# View logs by container ID prefix
ctst logs a1b2c3d4Stop one or more containers and clean up their associated resources.
ctst stop [OPTIONS] [CONTAINERS...]
| Argument / Flag | Description | Default |
|---|---|---|
CONTAINERS... |
Container IDs or names to stop | All running containers |
-f, --force |
Skip graceful shutdown — send SIGKILL immediately |
false |
Inherits all global options.
ctst stop initiates a shutdown of the specified containers (or all running containers if none are specified).
- SIGTERM — Send
SIGTERMto the container's init process. - Grace period — Wait up to 10 seconds for the process to exit.
- SIGKILL — If the process has not exited, send
SIGKILL.
With --force, step 1 is skipped and SIGKILL is sent immediately.
After the process exits, ctst stop performs cleanup:
- Cgroup removal — Delete the container's cgroup directory from
/sys/fs/cgroup. - Mount teardown — Unmount OverlayFS layers and any bound volumes.
- State file update — Mark the container as
stoppedin the state index. - Network cleanup — Remove virtual network interfaces.
| Code | Meaning |
|---|---|
0 |
All specified containers stopped successfully |
1 |
One or more containers failed to stop |
3 |
Permission denied |
4 |
Container not found |
# Stop all running containers gracefully
ctst stop
# Stop specific containers by name
ctst stop api db
# Force kill a stuck container
ctst stop --force legacy-worker
# Stop a container by ID prefix
ctst stop a1b2c3Manage the local image catalog.
ctst images [OPTIONS]
| Flag | Description |
|---|---|
-l, --list |
List all locally stored images |
--remove <ID> |
Remove an image by its SHA-256 ID |
Inherits all global options.
ctst images provides operations on the local image store located at .containust/images/ (project-local). Images are composed of content-addressable OverlayFS layers identified by their SHA-256 hash.
$ ctst images --list
IMAGE ID SOURCE SIZE CREATED LAYERS
sha256:a1b2c3d4e5f6a7b8 file:///opt/images/myapp 42.8 MiB 2026-02-20 14:30 3
sha256:e5f6a7b8c9d0e1f2 tar:///backups/pg15.tar 89.1 MiB 2026-02-19 09:15 5
sha256:c9d0e1f2a3b4c5d6 file:///opt/images/redis 12.3 MiB 2026-02-18 22:00 2
Image IDs follow the sha256:<hex> convention. The full ID is 64 hex characters; short prefixes (minimum 12 characters) are accepted anywhere a full ID is required, provided they are unambiguous.
Images and their layers are stored under .containust/images/ (project-local). Each image directory contains its layer tarballs and a manifest file linking layers to their content hashes.
| Code | Meaning |
|---|---|
0 |
Operation succeeded |
1 |
Operation failed (I/O error, image in use) |
4 |
Image ID not found |
# List all local images
ctst images --list
# Remove an image by full ID
ctst images --remove sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
# Remove an image by short ID prefix
ctst images --remove sha256:a1b2c3d4e5f6
# List images using a custom state file
ctst images --list --state-file /tmp/dev-state.jsonContainust manages container lifecycle through a local JSON state file instead of a daemon.
Default: .containust/state.json (project-local, next to the .ctst file)
Override: --state-file <PATH> or CONTAINUST_STATE_FILE environment variable.
{
"version": 1,
"containers": [
{
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"name": "api",
"state": "running",
"pid": 48230,
"created_at": "2026-02-24T10:30:00Z",
"image": "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"limits": {
"memory_bytes": 268435456,
"cpu_shares": 1024
}
},
{
"id": "f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c",
"name": "db",
"state": "running",
"pid": 48201,
"created_at": "2026-02-24T10:29:58Z",
"image": "sha256:e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
"limits": {
"memory_bytes": 536870912,
"cpu_shares": 2048
}
}
]
}ctst run— Entries added when containers are created.ctst stop— Entries updated tostoppedstate; PID cleared.ctst build— No state file changes (build is stateless).- Container exit — On next
ctst psinvocation, stale PIDs are detected and states corrected.
If the state file is corrupted or missing:
ctstcreates a new empty state file on next invocation.- Orphaned containers (running processes with no state entry) can be discovered via
/sys/fs/cgroupand re-registered. - Back up the state file before manual edits:
cp state.json state.json.bak.
Every container receives a UUID v4 identifier at creation time (e.g., a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d). The full UUID is 36 characters including hyphens.
The container name is derived from the COMPONENT name in the .ctst file (e.g., api, db, cache). Names are unique within a single deployment.
All commands that accept a container reference (exec, stop) resolve in the following order:
- Exact name match —
api - UUID prefix match —
a1b2c3d4(minimum 8 characters, must be unambiguous) - Full UUID match —
a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
If a prefix is ambiguous, the command fails with an error listing the matching containers.
Summary of all exit codes used across ctst commands.
| Code | Meaning | Commands |
|---|---|---|
0 |
Success | All |
1 |
General error | All |
2 |
Usage / argument error | All |
3 |
Permission denied (namespace/cgroup creation) | run, exec, stop |
4 |
Resource not found (file, image, container) | All |
126 |
Command cannot execute (permission denied inside container) | exec |
127 |
Command not found inside container | exec |
| Variable | Description | Default |
|---|---|---|
CONTAINUST_STATE_FILE |
Path to the state index file | .containust/state.json (project-local) |
CONTAINUST_LOG |
Tracing filter directive (e.g., info, debug, containust_runtime=trace) |
warn |
CONTAINUST_OFFLINE |
Set to 1 to enable offline mode (equivalent to --offline) |
unset |
CONTAINUST_CACHE_DIR |
Global cache directory for immutable VM assets | ~/.containust/cache |
CONTAINUST_IMAGE_STORE |
Directory for cached images and layers | .containust/images (project-local) |
CONTAINUST_ROOTFS_DIR |
Directory for container rootfs mounts | .containust/rootfs (project-local) |
CLI flags take precedence over environment variables.
Cause: Creating Linux namespaces requires either root privileges or unprivileged user namespace support.
Fix:
# Run with sudo
sudo ctst run
# Or enable unprivileged user namespaces (if supported by your kernel)
sudo sysctl -w kernel.unprivileged_userns_clone=1Cause: The kernel is not mounted with cgroups v2 unified hierarchy, or the system is using cgroups v1.
Fix:
# Check if cgroups v2 is mounted
mount | grep cgroup2
# If not, add to kernel boot params:
# systemd.unified_cgroup_hierarchy=1
# Then reboot.Cause: Another ctst process is holding a lock on the state file, or a previous invocation crashed without releasing the lock.
Fix:
# Check for running ctst processes
ps aux | grep ctst
# If no processes are running, remove the stale lock
rm .containust/state.json.lockCause: The image source path in the .ctst file does not exist, or the image has not been built yet.
Fix:
# Verify the source path exists
ls -la /opt/images/myapp
# Build images first
ctst build
# Then run
ctst runCause: The container process is ignoring SIGTERM (e.g., it traps or ignores the signal).
Fix:
# Force kill the container
ctst stop --force stuck-container
# If that fails, find and kill the process manually
ctst ps --all # note the PID from the state file
sudo kill -9 <PID>Cause: Another process (or a previously stopped container that was not cleaned up) is binding the requested port.
Fix:
# Find what is using the port
sudo ss -tlnp | grep :8080
# Stop the conflicting process, then retry
ctst runCause: The container exceeded its cgroup memory limit and was killed by the OOM killer.
Fix:
# Increase the memory limit in your .ctst file
# memory = "512MB" → memory = "1GB"
# Check dmesg for OOM events
dmesg | grep -i "out of memory"Cause: --offline (or CONTAINUST_OFFLINE=1) is active and the .ctst file references remote imports or images that are not locally cached.
Fix:
# Build with network access first to populate the cache
ctst build
# Then run in offline mode
ctst run --offline
# Or remove --offline to allow network access
ctst runConvert a docker-compose.yml file to Containust .ctst format.
ctst convert [OPTIONS] [FILE]Parses a Docker Compose YAML file and emits the equivalent .ctst composition language output. This provides a fast migration path from Docker Compose to Containust.
The converter handles:
- Services to
COMPONENTblocks depends_ontoCONNECTstatements with auto-wiringportstoport/portsproperties withEXPOSEcommentsvolumestovolume/volumespropertiesenvironmenttoenvmaps (Docker${}vars mapped to${secret.*})restartpolicies (no->"never",unless-stopped->"always")healthchecktohealthcheckblocks (stripsCMD/CMD-SHELLprefixes)mem_limit/deploy.resources.limits.memorytomemorywith size conversiondeploy.resources.limits.cpustocpushares (multiplied by 1024)command,entrypoint,working_dir,user,hostname,read_only,networks- Docker Hub images converted to
tar://placeholders with export instructions
| Argument | Description | Default |
|---|---|---|
FILE |
Path to the docker-compose.yml file | docker-compose.yml |
| Flag | Description |
|---|---|
-o, --output <PATH> |
Write output to a file instead of stdout |
| Code | Meaning |
|---|---|
0 |
Conversion succeeded |
1 |
File not found or YAML parse error |
# Convert and print to stdout
ctst convert
# Convert a specific file
ctst convert docker-compose.prod.yml
# Convert and save to a .ctst file
ctst convert -o infrastructure.ctst
# Convert a specific file to a specific output
ctst convert docker-compose.yml -o app.ctst
# Pipe to ctst plan for immediate preview
ctst convert -o app.ctst && ctst plan app.ctstGiven this docker-compose.yml:
services:
api:
image: myapp:latest
ports:
- "8080:80"
environment:
DATABASE_URL: postgres://db:5432/app
depends_on:
- db
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: unless-stoppedRunning ctst convert produces:
// Auto-generated by: ctst convert
// Source: docker-compose.yml
// Components: 2
//
// Review image sources — Docker Hub references have been converted
// to tar:// placeholders. Export images with:
// docker save <image> -o /opt/images/<name>.tar
COMPONENT api {
image = "tar:///opt/images/myapp.tar"
port = 80
// EXPOSE 8080:80
env = {
DATABASE_URL = "postgres://db:5432/app"
}
}
COMPONENT db {
image = "tar:///opt/images/postgres.tar"
volume = "pgdata:/var/lib/postgresql/data"
env = {
POSTGRES_PASSWORD = "${secret.DB_PASSWORD}"
}
restart = "always"
}
// Dependencies (converted from depends_on).
// CONNECT auto-injects _HOST, _PORT, _CONNECTION_STRING env vars.
CONNECT api -> db
- Export Docker images to tar archives:
docker save <image> -o /opt/images/<name>.tar - Review image URIs — replace
tar://placeholders with actual paths - Remove duplicate env vars —
CONNECTauto-injects_HOST,_PORT,_CONNECTION_STRING - Add health checks if not present in the original compose file
- Test with
ctst plan app.ctstbefore deploying
Manage the platform VM used on macOS and Windows for running Linux containers.
ctst vm <SUBCOMMAND>
| Subcommand | Description |
|---|---|
start |
Boot the lightweight Alpine Linux VM via QEMU |
stop |
Gracefully shut down the VM |
On macOS and Windows, Containust requires a lightweight Linux VM to provide kernel-level container primitives (namespaces, cgroups v2, OverlayFS). The VM uses QEMU with hardware acceleration (HVF on macOS, Hyper-V/WHPX on Windows) and boots an Alpine Linux image (~50MB) in under 2 seconds.
The VM is automatically started on the first container operation if not already running. Use ctst vm start to pre-boot the VM for faster first-container startup.
On Linux, this command is a no-op and prints a message indicating the native backend is in use.
| Platform | Requirement |
|---|---|
| macOS | QEMU installed via brew install qemu |
| Windows | QEMU installed via winget install QEMU.QEMU or the QEMU installer |
| Linux | No VM required — native backend used |
| Code | Meaning |
|---|---|
0 |
VM started/stopped successfully (or native backend on Linux) |
1 |
VM failed to start or stop |
# Pre-boot the VM for faster container operations
ctst vm start
# Shut down the VM when done
ctst vm stop
ctst — Built with Rust. Designed for sovereignty.