SuperAPI AI Directory

write-appcipe

General & other

View repository

Updated 10-16 09:44

About this skill

Writing appcipe.yml

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) or image: redis:7.2-alpine(registry ref, auto-detected).
    • Full (source choose 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: dockerfile may also add context:(build context directory, omitted = directory containing the Dockerfile) and build_args: { K: V }。requires docker/podman/nerdctl on the build machine; reproducibility is not guaranteed (for reproducibility use source: image to pin the @sha256 digest). Docker is still not required at runtime.
  • 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 has healthcheck, 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 through sh -c). Executed inside the container, exit 0 = healthy. Example:["CMD", "redis-cli", "ping"]、["CMD", "pg_isready", "-U", "postgres"]。
    • interval(default 2s)、timeout(default 5s)、start_period(default 0s): accepts <n>ms/<n>s/<n>m or a bare integer (seconds).
    • retries(default 10): 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, default false): opt-in GPU passthrough. Can be written as false(off) / true(all GPUs) / [0, 2](bind only the specified NVIDIA cards, hard isolation); also accepts the string all/none(Docker --gpus all as a customary alias, equivalent to true/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-matching libcuda/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, use env of CUDA_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 use 127.0.0.1:<port> (e.g., app connecting to db set env: { 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 under bridge/internal are truly not exposed externally (the service only listens on lo in 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 default bridge . 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 accessuse internal。
  • ⚠️ Only explicitly writing network: shared reverts 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+gosu switches 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 (official redis has 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 the newuidmap/newgidmap(uidmap package) 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 (official redis has 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 may chown fail and not start——in this case install uidmap and add a subuid range, or switch toan image that runs directly as container root without chown (e.g., self-built alpine + apk add redis). Reference examples/demo/db/Dockerfile。
  • format usehyphen:docker-archive / oci-archive(the underscore form is also accepted, but the documentation uniformly uses hyphens).
  • registry ref Must be pinned(redis:7.2-alpine or @sha256:…);latest/without a tag will be rejected by validation. Private registry authentication is not yet supported; you can still docker pull + docker save go 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

  1. cargo run -p chefer-cli -- check <appcipe.yml> → exit 0。
  2. Every image tar pointed to exists (docker save generated).
  3. persist_path、mounts 's in-container paths all use / beginning.
  4. host ports are unique across all apps; list only services that truly need external exposure ports。
  5. For cross-service connections use 127.0.0.1:<port> + env, do not assume DNS service names.