Image format & store
#Filename grammar
<name>-<semver>-<os>-<arch>.img
ffmpeg-6.1.0-linux-x64.imgStrict and tool-enforced: ply build emits it automatically; names may not
contain - followed by a digit (parsing ambiguity). The filename is an
identity claim; the lockfile's sha256 is the proof. Anyone can name
a file node-22.6.0-linux-x64.img; only the right bytes will hash-verify.
Supported platforms: linux-x64, linux-arm64.
#The image file
A ply image is a deterministic squashfs: entries sorted, timestamps zeroed, ownership fixed, zstd-compressed. The same input directory produces a byte-identical file, every time — content-addressing depends on it, and you can prove a build honest by rebuilding it.
Inside, at a known path, lives the embedded manifest (and for app images the lockfile) — an image is self-describing. Images mount directly (no extraction); on hosts that can't loop-mount, ply extracts to a plain store directory with the same hash identity.
#The store
/var/lib/ply/
├── store/
│ └── sha256:<hash>/ # immutable; the dir name proves the content
└── volumes/
└── <app>/<name>.<n>/ # named volumes, per instance slot
/run/ply/ # tmpfs — gone on reboot
├── state/<app>.<n>.json # pid, ip, ports per instance
└── instances/<app>.<n>/ # overlay upper/work/merged dirs- Same hash = one copy: dedup by construction, across every app on the host
ply gc= reachability from installed/running apps' lockfiles; anything unreferenced is deletedrm -rf /var/lib/plyis a complete factory reset — ply never touches/bin,/usr, or/etcbeyond its managed hosts entries- Rootless mode keeps the same layout under your home directory
#How images compose at run time
Dependencies are separate store entries. At run, ply loop-mounts each
squashfs read-only and stacks them with overlayfs — app on top, base at the
bottom, order derived deterministically from the dependency graph. Each
package owns /opt/<name>-<version>/, so layers can't conflict. A writable
tmpfs upper layer catches scratch writes; volumes bind-mount over their
declared paths.
You declare a set; ply derives the stack — the inverse of Docker, where the stack is authored and baked at build time.
#Registry file protocol
A registry is a file host. The full protocol:
<base-url>/<name>-<version>-<os>-<arch>.img # GET an image
<base-url>/index.json # optional: list for range resolutionindex.json is a JSON array of image filenames present at that prefix:
["node-22.5.0-linux-x64.img", "node-22.6.0-linux-x64.img"]The official registry additionally publishes state.json at the bucket
root — a machine-readable snapshot (packages, versions, sizes, timestamps)
that powers registry.plybox.sh. Neither file
is needed to fetch pinned versions; fetching is always just a GET plus a
hash check.