SKILL.md
撰寫 appcipe.yml
appcipe.yml 是 Chefer 的「食譜」:描述一或多個 Docker/OCI 映像 + 執行參數, chefer build 會把它們打包成單一執行檔(免裝 Docker/容器引擎,雙擊即跑)。
權威驗證規則在 crates/appcipe-spec/src/validate.rs;完整契約在 docs/DESIGN.md §3/§6; 有註解的範例見 examples/appcipe.yml、可運作的多服務範例見 examples/demo/。 改完一定要 cargo run -p chefer-cli -- check <appcipe.yml> 驗證(會一次列出所有錯誤)。
整體流程(先講給使用者聽)
docker build / docker pull → docker save -o x.tar <image> # 取得 image tar
寫 appcipe.yml(image 指向那些 tar)
chefer build appcipe.yml --out dist # 產出單檔
image 來源可為 tar(docker save/OCI archive)、registry ref(釘版 tag 或 @sha256;公開 image 匿名拉取,私有 registry 用 docker login 的明文 auths 憑證或 CHEFERREGISTRYAUTH=user:pass——外部 credential helper 不支援)或 Dockerfile(打包機需有 docker/podman/nerdctl)。
最小可用範例
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
頂層欄位
| 欄位 | 必填 | 規則/用途 |
|---|---|---|
version |
✓ | 固定 "0.1"。 |
name |
✓ | [A-Za-z][A-Za-z0-9-]*,≤64。輸出檔名 <name><target>[.exe]、資料目錄名。 |
app_version |
純顯示/中繼資料:check/build/inspect 會印、執行時 log 一次。不影響打包,容器內程式讀不到。要在程式裡用版本就自己塞進服務的 env:。 |
|
old_names |
字串清單,每項須是單一目錄名(同 name 規則,不可含 / \ : .. 或絕對路徑)。data dir 不存在時,依序找舊名目錄自動改名遷移。 |
|
data_dir |
覆蓋持久化資料的父目錄;未設用平台預設(Win %LOCALAPPDATA%\{name}、mac ~/Library/Application Support/{name}、Linux $XDGDATAHOME 或 ~/.local/share/{name})。 |
|
crash |
目前僅 failfast(任一服務非 0 退出→整個 app 以該碼退出)。舊欄位名 crashpolicy 仍接受。 |
|
network |
bridge(預設)|internal|shared。bridge=app 專屬網路、只開宣告的 ports、可出網;internal=同 bridge 但無對外網路;shared=共用 host 網路(不隔離、舊行為)。見下方「內部網路」。 |
|
console |
auto(預設)|shown|hidden。共用主控台(彙整日誌 + Ctrl+C 的終端視窗)顯示策略。auto=有 terminal/both→顯示、只有 gui→隱藏、全無介面→顯示。hidden 僅當 app 另有 gui 或 terminal/both 可關閉才允許(全無介面用 hidden 會驗證失敗)。隱藏只在 Windows 雙擊啟動且獨佔該 console 時生效。 |
|
services |
✓ | 服務字典,見下。 |
services.<name> 欄位
service 名規則:[a-z][a-z0-9_]*,≤32。
image(必填)。寫法:
- 短寫:image: ./x.tar(tar 路徑)或 image: redis:7.2-alpine(registry ref,自動判別)。 - 完整(source 三選一): ``yaml 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 另可加 context:(build context 目錄,省略 = Dockerfile 所在目錄)與 build_args: { K: V }。需打包機上有 docker/podman/nerdctl;不保證可重現(要可重現用 source: image 釘 @sha256` digest)。執行期仍不需 Docker。
cmd:字串或陣列,覆蓋映像的 CMD(不覆蓋 ENTRYPOINT)。有效命令 = entrypoint + (cmd 或 image CMD)。workdir:容器內工作目錄。env:key: value;key 須符合[A-Za-z][A-Za-z0-9]*。會覆蓋映像自帶的同名 env。persist_path:要持久化的容器內絕對路徑(須以/開頭)。實體落在
{data_dir 或預設}/{name}/data/{service}/,跨重啟保留。沒設=退出即清空。
ports:["host:guest[/proto]"](proto 預設 tcp)。同一 app 內 host 埠不可重複。
只有列在這裡的埠才會被 chefer 代理到 host。
mounts:["<host路徑>:<容器內絕對路徑>"];容器內路徑須以/開頭;host 路徑在 build 時須存在。interface_mode:gui | terminal | both | none(預設 none)。全 app 最多一個 terminal/both。depends_on:服務名清單。決定啟動順序;若被依賴的服務有healthcheck,依賴者會等它 healthy 才啟動(wait-until-ready),否則等它 spawn 即可。須指向存在的服務、不可循環、不可自指。healthcheck(選填,對齊 Docker HEALTHCHECK):
- test:命令字串(= sh -c)或陣列(["CMD", ...] 直接 argv;["CMD-SHELL", "..."] 走 sh -c)。在容器內執行,exit 0 = 健康。例:["CMD", "redis-cli", "ping"]、["CMD", "pgisready", "-U", "postgres"]。 - interval(預設 2s)、timeout(預設 5s)、startperiod(預設 0s):接受 <n>ms/<n>s/<n>m 或裸整數(秒)。 - retries(預設 10):連續失敗幾次(扣除 startperiod 寬限)才算 unhealthy → failfast 拆掉整個 app。 - 啟動目前序列化:一個服務的 healthcheck 會擋住其後所有服務(含不相依者),通常無感;之後會改成只擋實際 dependents。
gpu(選填,預設false):opt-in GPU passthrough。可寫false(關)/true(全部 GPU)/[0, 2](只綁指定 NVIDIA 卡,硬隔離);也接受字串all/none(Docker--gpus all慣用別名,等同true/false)。開啟後 guest-agent 把 host GPU 裝置節點(/dev/nvidia*、/dev/dri、AMD 的/dev/kfd、WSL2 的/dev/dxg)bind 進這個服務的容器 → CUDA/ROCm/OpenCL 計算與 NVENC/NVDEC 視訊。驅動 userspace:**原生 NVIDIA 由 chefer 自動注入 host 相符的libcuda/libnvidia-(image 免自帶);WSL2 由/usr/lib/wsl/lib+/usr/lib/wsl/drivers提供;AMD/Intel 由映像自帶(rocm/、intel/oneapi)。只在原生 Linux 與 Windows WSL2 後端可行(WHP micro-VM/macOS VM 服務啟動時明確報錯)。gpu: [i,…]卡索引硬隔離僅原生 NVIDIA 有效——WSL2/AMD/Intel 請改用env的CUDAVISIBLEDEVICES/HIPVISIBLEDEVICES/ZEAFFINITYMASK(軟選擇)。只給真的需要 GPU 的服務開。實機驗證:CUDA/PyTorch/NVENC/nvidia-smi(RTX 4070 + WSL2 GT 1030);OpenGL/Vulkan 算繪**無頭容器不支援。
內部網路與「不對外暴露」(重要、實測過)
- 整個 app 跑在自己的 network namespace(預設
network: bridge)。服務間互連用127.0.0.1:<port>
(例:app 連 db 設 env: { DBHOST: "127.0.0.1", DBPORT: "6379" })。
- 只有列在某服務
ports:的埠才會被代理到 host;未宣告的埠在bridge/internal下真正不對外
(服務只在 app netns 的 lo 監聽 → host 連不到、WSL2 wslrelay 也看不到)。所以「不列 ports 的 db」 在預設 bridge 下確實內部專用。已於原生 Linux 與實機 WSL2 驗證。
- 想讓服務能出網(裝套件、call 外部 API)用預設
bridge;只要服務間互通、不需出網用internal。 - ⚠️ 只有顯式寫
network: shared才回到舊的「共享 host 網路、未宣告埠也可從 host 連到」行為——
這時才不要向使用者保證 db 不可達。
真實映像的陷阱(實測過)
- 官方 redis/postgres/nginx 等的 entrypoint 常在以 root 執行時
chown+gosu切到
服務專用 uid(如 999)。這類映像在 WSL2、macOS VM、以及原生 Linux 以 root 執行時 可直接使用——這些後端讓服務以真實 root 執行(不開 user namespace),chown/gosu 到任何 uid 都成功(官方 redis 已在 WSL2 實測通過)。原生 Linux 的 rootless 路徑(以非 root 使用者執行單檔)也支援:host 有 newuidmap/newgidmap(uidmap 套件)且使用者在 /etc/subuid//etc/subgid 有委派範圍時(多數發行版預設都有),guest-agent 走範圍映射 (同 rootless podman),chown/gosu 映像照跑(官方 redis 已以非 root 實測)。只有 無委派環境(無 uidmap 或無 subuid 範圍)會退回單一 uid 映射,那些映像可能 chown 失敗而起不來——此時裝 uidmap 並補 subuid 範圍,或改用以容器 root 直接執行、不 chown 的映像(例:自建 alpine + apk add redis)。參考 examples/demo/db/Dockerfile。
format用連字號:docker-archive/oci-archive(底線形式也接受,但文件統一用連字號)。- registry ref 必須釘版(
redis:7.2-alpine或@sha256:…);latest/未帶 tag 會被驗證拒絕。私有 registry 認證尚未支援,仍可docker pull+docker save走 tar。
多服務範例(app + db,內部連線 + 持久化)
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 仍應自行重試連線)
收尾檢查清單
cargo run -p chefer-cli -- check <appcipe.yml>→ exit 0。- 每個
image指向的 tar 都存在(docker save產生)。 persist_path、mounts的容器內路徑都以/開頭。- host 埠全 app 唯一;只有真要對外的服務才列
ports。 - 跨服務連線用
127.0.0.1:<port>+env,不要假設 DNS 服務名。