The pod deploy substrate for OpenCharly — target: pod, the DEFAULT substrate
(a deployment run as a container image on the resolved container engine), served
out-of-process (deploy:pod).
The launch argv and the supervision file come from the ENGINE provider-class ops
(start_plan / unit_emit, spec/container/engine_ops.go), so the substrate is
engine-agnostic by construction: engine.run=podman uses podman's quadlet run
mode, and engine.run=containerd (driven through nerdctl) uses a generated
systemd unit that wraps the foreground nerdctl run argv, carrying the runtime
handler and daemon address/namespace from the capability table. Rootful containerd
is refused with a remedy (system-scope units and a rootful image store are not
implemented here) rather than silently run rootless.
The plugin is a standalone Go module the charly loader host-builds and serves
over go-plugin gRPC. It is the pod-substrate sibling of plugin-deploy-vm.
| Capability | Surface |
|---|---|
deploy:pod |
the pod: deploy substrate (the default target) |
Unlike deploy:vm (whose plugin WALKS the plan inside the guest), pod bakes its
install steps INTO the image at BUILD time:
- The pod lifecycle (
lifecycle.go, this plugin) builds the overlay container image HOST-SIDE inPrepareVenue: the host prep returns theOverlayBuildReplyenvelope (resolved project + plans + base-image metadata + per-overlay-candy security + parent bind-mount volumes), and the candy importssdk/deploykit+buildkit+kitdirectly, constructs thedeploykit.Generatoritself, renders the overlay Containerfile in its own code, and runspodman build+ the deploy-name alias tag via its served host executor. Each per-step Containerfile fragment is rendered host-side over the genericstep-emithost-builder. - So there is NO per-step venue walk for pod: this plugin's
Invokereturns an EMPTYDeployReply— pod teardown ischarly remove+ drop overlay images, owned by the host lifecycle hook'sPostTeardown. - The pod config-WRITE also lives here:
OpConfigWriterenders the quadlet/.pod/sidecar/tunnel file contents and writes them at the host-resolved absolute paths + exact modes. For thesystemd-unitrun mode (containerd), the file is the engineunit_emitservice, plus the tunnel companion unit when a tunnel is configured (sidecars require the quadlet run mode).
Compose the plugin candy in a project's candy: list:
- '@github.com/opencharly/plugin-deploy-pod/candy/plugin-deploy-pod:<tag>'Then author a pod: deploy (the default substrate):
my-deploy:
pod:
image: my-boxcandy/plugin-deploy-pod/— the plugin module:plugin.go(the deploy provider +NewProvider()/NewMeta()/Invoke),lifecycle.go(the overlay build + venue lifecycle ops),config_write.go/config_setup.go/config_remove.go(the config surface),resolve*.go(the project/ref/sidecar resolution),schema/pod.cue(the self-contained#DeployPodPlugin), the Go tests,cmd/serve/main.go.charly.yml— the root project manifest (discover: candy)..github/workflows/tag-on-merge.yml— CalVer tag +CHANGELOG/on merge.
- Owning skill:
/charly-pod:pod— thekind: pod/ deploy entity schema reference. /charly-core:deploy—charly deploy add/del, quadlet generation, tunnels, and per-machine deploy overlays./charly-internals:plugin— the plugin/provider model, including thedeployprovider class.opencharly/charly— the charly CLI.
Every shell step this plugin drives on the venue runs through one helper (venueRun in
venue_exec.go) which, on failure, returns an error carrying the venue's own cause — its
stderr, or its exit code when it printed nothing — behind the operation and the artifact:
plugin-deploy-pod deploy-name alias tag (my-deploy): Error: ghcr.io/opencharly/x:2026.280.0000: image not known
Before this change the same failure reached the operator as command exited 125, with nothing
naming the image, the reason, or the venue (opencharly/plugin-deploy-pod#19). Do not reintroduce a
silent venue step: VenueRunSilent discards both streams, and this plugin no longer calls it
anywhere (pinned by venue_exec_test.go).