appcipe.yml is Chefer's "recipe": it describes one or more Docker/OCI images + run parameters,
chefer build and packages them into asingle executable(no need to install Docker/container engine, double-click to run).
The authoritative validation rules are in crates/appcipe-spec/src/validate.rs; the full contract is in docs/DESIGN.md §3/§6;
for an annotated example see examples/appcipe.yml, and for a working multi-service example see examples/demo/。
After making changes you must always cargo run -p chefer-cli -- check <appcipe.yml> validate(it lists all errors at once).
Overall flow (explain to the user first)
docker build / docker pull → docker save -o x.tar <image> # 取得 image tar
寫 appcipe.yml(image 指向那些 tar)
chefer build appcipe.yml --out dist # 產出單檔
The image source can be tar(docker save/OCI archive)、registry ref(a pinned tag or @sha256; public images are pulled anonymously,private registry uses docker login 's plaintext auths credentials or CHEFER_REGISTRY_AUTH=user:pass—external credential helpers are not supported) or Dockerfile(the packaging machine needs docker/podman/nerdctl).
Minimal working example
version: "0.1" # 必填,目前固定 "0.1"
name: MyApp # 必填;[A-Za-z][A-Za-z0-9_-]*,≤64;也是輸出檔名與資料夾名
services:
web:
image: ./images/web.tar # 短寫=tar 路徑(相對 appcipe.yml)
ports: ["8080:8080"] # host:guest,從 host 連 127.0.0.1:8080
Top-level fields
| Field | Required | Rules/Purpose |
|---|---|---|
version |
✓ | Fixed "0.1"。 |
name |
✓ | [A-Za-z][A-Za-z0-9_-]*, ≤64. Output filename <name>_<target>[.exe], data directory name. |
app_version |
Display/metadata only: check/build/inspect will print it, log it once at runtime.Does not affect packaging; programs inside the container cannot read it. To use the version in the program, put it into the service's env:。 |
|
old_names |
String list, each item must be asingle directory name(same name rules, must not contain / \ : .. or an absolute path). When the data dir does not exist, it looks for old-name directories in order and automatically renames/migrates them. |
|
data_dir |
Overrides the parent directory of persistent data; if unset, the platform default is used (Win %LOCALAPPDATA%\{name}、mac ~/Library/Application Support/{name}、Linux $XDG_DATA_HOME or ~/.local/share/{name})。 |
|
crash |
Currently only fail_fast(if any service exits non-zero → the whole app exits with that code). The old field name crash_policy is still accepted. |
|
network |
bridge(default)|internal|shared。bridge=app-dedicated network, only declared ports opened, can access the internet;internal=same bridge but no external network;shared=share the host network (no isolation, legacy behavior). See "Internal network" below. |
|
console |
auto(default)|shown|hidden. Shared console (aggregated logs + Ctrl+C terminal window) display policy.auto=terminal/both→show, gui only→hide, no interface at all→show.hidden is allowed only when the app has another gui or terminal/both that can be closed (using hidden with no interface at all will fail validation). Hiding takes effect only on Windows when launched by double-click and the console is exclusively owned. |
|
services |
✓ | Service dictionary, see below. |
services. fields
service name rules:[a-z][a-z0-9_]*,≤32。
image(required). Formats:- Short form:
image: ./x.tar(tar path) orimage: redis:7.2-alpine(registry ref, auto-detected). - Full (
sourcechoose one of three):image: source: tar # tar | image | dockerfile file: ./images/x.tar # tar=tar 路徑;image=registry ref(須釘版、非 latest);dockerfile=Dockerfile 路徑 format: docker-archive # auto | docker-archive | oci-archive(**用連字號**,非底線;僅 source=tar 有意義) platform: linux/amd64 # linux/amd64 | linux/arm64(windows/amd64 不支援執行) source: dockerfilemay also addcontext:(build context directory, omitted = directory containing the Dockerfile) andbuild_args: { K: V }。requires docker/podman/nerdctl on the build machine; reproducibility is not guaranteed (for reproducibility usesource: imageto pin the@sha256digest). Docker is still not required at runtime.
- Short form:
cmd: string or array, overrides the image's CMD (does not override ENTRYPOINT). Effective command = entrypoint + (cmd or image CMD).workdir: working directory inside the container.env:key: value; key must match[A-Za-z_][A-Za-z0-9_]*. Overrides the image's own env with the same name.persist_path: to be persistedabsolute path inside the container(must start with/). The actual data lives in{data_dir 或預設}/{name}/data/{service}/, retained across restarts. If unset = cleared on exit.ports:["host:guest[/proto]"](proto defaults to tcp).host ports must be unique within the same app. Only ports listed here will be proxied to the host by chefer.mounts:["<host路徑>:<容器內絕對路徑>"]; the path inside the container must start with/; the host path must exist at build time.interface_mode:gui | terminal | both | none(default none).at most one terminal/both per app。depends_on: list of service names. Determines startup order; if a depended-on service hashealthcheck, the dependent willwait for it to be healthy before starting(wait-until-ready), otherwise it just waits for it to spawn. Must point to existing services, must not be cyclic, must not be self-referential.healthcheck(optional, aligned with Docker HEALTHCHECK):test: command string (=sh -c) or array (["CMD", ...]direct argv;["CMD-SHELL", "..."]goes throughsh -c). Executed inside the container, exit 0 = healthy. Example:["CMD", "redis-cli", "ping"]、["CMD", "pg_isready", "-U", "postgres"]。interval(default2s)、timeout(default5s)、start_period(default0s): accepts<n>ms/<n>s/<n>mor a bare integer (seconds).retries(default10): how many consecutive failures (excluding the start_period grace) before it counts as unhealthy → fail_fast tears down the entire app。- startup is currentlyserialized: one service's healthcheck will block all services after it (including unrelated ones), usually imperceptible; this will later be changed to block only actual dependents.
gpu(optional, defaultfalse): opt-in GPU passthrough. Can be written asfalse(off) /true(all GPUs) /[0, 2](bind only the specified NVIDIA cards, hard isolation); also accepts the stringall/none(Docker--gpus allas a customary alias, equivalent totrue/false). Once enabled, the guest-agent binds the host GPU device nodes (/dev/nvidia*、/dev/dri, AMD's/dev/kfd, WSL2's/dev/dxg) intothis service's container → CUDA/ROCm/OpenCL compute and NVENC/NVDEC video. Driver userspace:native NVIDIA has chefer automatically inject the host-matchinglibcuda/libnvidia-*(the image does not need to bundle it); WSL2 is provided by/usr/lib/wsl/lib+/usr/lib/wsl/drivers;AMD/Intel is bundled by the image (rocm/*、intel/oneapi)。only works on native Linux and the Windows WSL2 backend(WHP micro-VM / macOS VM explicitly errors out at service startup).gpu: [i,…]Card index hard isolationOnly effective on native NVIDIA——For WSL2/AMD/Intel, useenvofCUDA_VISIBLE_DEVICES/HIP_VISIBLE_DEVICES/ZE_AFFINITY_MASK(soft selection).Only enable it for services that truly need the GPU. Verified on real hardware: CUDA/PyTorch/NVENC/nvidia-smi (RTX 4070 + WSL2 GT 1030); OpenGL/Vulkan renderingis not supported in headless containers.
Internal network and "not exposed externally" (important, tested)
- The entire app runs in its own network namespace(default
network: bridge). For service-to-service interconnection use127.0.0.1:<port>(e.g., app connecting to db setenv: { DB_HOST: "127.0.0.1", DB_PORT: "6379" })。 - Only ports listed in a service's
ports:will be proxied to the host;undeclared ports underbridge/internalare truly not exposed externally (the service only listens onloin the app netns → the host cannot connect, and WSL2 wslrelay cannot see it either). So a "db with no ports listed" is indeed internal-only under the defaultbridge. Verified on native Linux and real WSL2. - If you want the serviceto have outbound network access(installing packages, calling external APIs), use the default
bridge;If you only need service-to-service communication and no outbound network accessuseinternal。 - ⚠️ Only explicitly writing
network: sharedreverts to the old behavior of "sharing the host network, where undeclared ports can also be connected to from the host"—— only thendo notassure users that the db is unreachable.
Pitfalls of real images (tested)
- Official redis/postgres/nginx etc.'s entrypoint often, when running as root,
chown+gosuswitches to a service-specific uid (e.g., 999). Such images, under WSL2, macOS VM, and native Linux running as root, can be used directly——these backends let the service run as real root (without enabling user namespaces), and chown/gosu to any uid succeeds (officialredishas been tested and passed on WSL2). Native Linux's rootless path (running a single file as a non- root user) is also supported: when the host has thenewuidmap/newgidmap(uidmappackage) and the user has a delegated range in/etc/subuid//etc/subgid(most distributions have this by default), the guest-agent uses range mapping (same as rootless podman), and chown/gosu images run as usual (officialredishas been tested as non-root).Only non-delegated environments (no uidmap or no subuid range) fall back to a single uid mapping, and those images maychownfail and not start——in this case installuidmapand add a subuid range, or switch toan image that runs directly as container root without chown (e.g., self-builtalpine + apk add redis). Referenceexamples/demo/db/Dockerfile。 formatusehyphen:docker-archive/oci-archive(the underscore form is also accepted, but the documentation uniformly uses hyphens).- registry ref Must be pinned(
redis:7.2-alpineor@sha256:…);latest/without a tag will be rejected by validation. Private registry authentication is not yet supported; you can stilldocker pull+docker savego through tar.
Multi-service example (app + db, internal connection + persistence)
version: "0.1"
name: CheferDemo
app_version: "1.0.0" # 顯示用;要在程式裡用就另外塞 env
services:
db:
image: ./images/db.tar # 建議:自建 alpine+redis(CMD 直接跑、不 chown)
persist_path: /data # redis AOF 持久化到此
interface_mode: none
# 不列 ports:不主動對外(但見上面 v1 netns 缺口說明)
app:
image: ./images/app.tar
env:
DB_HOST: "127.0.0.1" # 內部網路:與 db 共享 localhost
DB_PORT: "6379"
ports: ["18080:8080"] # 唯一對外的埠
interface_mode: none
depends_on: [db] # 先啟動 db(app 仍應自行重試連線)
Final checklist
cargo run -p chefer-cli -- check <appcipe.yml>→ exit 0。- Every
imagetar pointed to exists (docker savegenerated). persist_path、mounts's in-container paths all use/beginning.- host ports are unique across all apps; list only services that truly need external exposure
ports。 - For cross-service connections use
127.0.0.1:<port>+env, do not assume DNS service names.