Every few months, someone sketches the same machine: one binary that hosts your code, reviews it, and ships it — no CI vendor, no PaaS bill, no glue. The newest sketch is Cave, a single-binary forge in Common Lisp whose 2026 design PRD made podman deploy a launch pillar: push, validate, review, merge, deploy, all on one box. Then the author did something more instructive than shipping it. In May 2026 he deferred all container build-and-deploy functionality indefinitely and told future users to bolt on Drone, Woodpecker, or shell scripts — the exact tools the PRD had promised to replace. The verdict, up front: the sketch validates git-push UX as the interface operators reach for, and the cut validates that deploy is a fleet problem wearing a single-box costume. Adopt the forge; get your deploy from a platform.
That verdict needs the receipts to hold: what the spec actually promised, what the cut removed, and a row-by-row accounting of who answers each pipeline need today. This post delivers all three, in that order.
What the PRD actually specified for deploy
Cave's design PRD is unusually honest about scope, which makes it unusually useful as a specimen. The target user is 3–10 developers already running Gitea or Forgejo plus external deploy scripts, already using podman, with one shared production host. Explicitly not the target: teams deploying to Kubernetes, Nomad, or a cloud PaaS. The whole machine is meant to sit comfortably on a 2-core, 2GB VPS, under 512MB RSS — against GitLab's 4GB-plus appetite, which the PRD names as the bloat it is running from.
The deploy design itself is a clean single-container-per-repo model. Configuration splits across two sources of truth: a versioned, code-reviewed .cave/deploy.toml in the repo controls what developers may change (Containerfile path, volume mounts), while operational settings that affect the host — deploy branch, container and host ports, build timeout, the deploy_enabled flag — live in the admin UI and database, so no commit can remap a host port. Secrets are stored AES-256-GCM encrypted, write-only through the UI, masked in logs, and written to a temp env-file that is deleted after podman run.
The deploy sequence is five steps with no ornament: build the image tagged cave/<org>/<repo>:<short-sha> from the merged commit; podman stop with a 30-second timeout; podman rm; podman run -d with the port mapping, env file, and volumes; record the result. Triggers are merge-to-deploy-branch or a manual Deploy button, both running the same sequence. Jobs queue FIFO with no coalescing, max depth three, one running at a time. Rollback re-deploys the previous image tag — with the current env vars, not historical ones, and without re-running hooks.
The sharpest edges are the ones the PRD states plainly rather than hiding. Deploy is best-effort process replacement: no readiness probes, no zero-downtime rotation, no restart policy managed by Cave (the operator wires systemd or Quadlet for supervision). One environment per repo — no staging-to-prod promotion. Images live in the Cave host's local podman storage; there is no registry, which is also why there is no multi-host deploy. Changing an env var neither rebuilds nor restarts anything; the operator clicks Restart.
The pre/post deploy hooks themselves — shell commands in deploy.toml, 60-second timeout, pre-deploy failure aborting cleanly — sit in P1, not launch. And the success metrics set the bar the design has to clear: under 90 seconds from merge to podman run exit, and over 80% of repos with a Containerfile using Cave deploy within 30 days.
Read as a whole, this is the best version of the single-box deploy story: declarative config, reviewed changes, bounded queue, honest rollback semantics. It is also, feature for feature, a list of the exact places where a second machine, a second environment, or a health check would force a redesign rather than an extension.
What got cut, and the failure modes it leaves on the table
The deferred-items document, committed May 3, 2026, is short and final: "All container build-and-deploy functionality is deferred indefinitely. Cave focuses on code review and forge capabilities." The database schema still carries the unused deploy columns — cave_deploys, cave_deploy_secrets, deploy_enabled — like foundation trenches for a wing that was never built. The project's README today confirms the shape: forge features implemented, automation runners and GitHub-Actions-compatible workflows experimental, and "Repo deployment / CD — build images, queue deploys, roll back, manage secrets" sitting under Planned.
The deferred list reads as an inventory of everything that turns "run a container" into "operate a deployment": a container registry (the prerequisite for multi-host deploy and external CI integration), environment promotion with per-environment secrets and branch mapping, health-checked zero-downtime deploys with readiness probes and rolling restarts, and detailed failure recovery. The failure-recovery section names the two modes the launch design would have shipped with: if podman run fails after a successful stop and remove, the service is down and the recovery is "attempt to restart previous image; if unavailable, service is down with no rollback available"; if that previous image was pruned, rollback itself fails with "image not found." Neither mode is a bug in the spec — both are documented behavior. They are simply what best-effort process replacement means once you write it down.
This is why the cut is more instructive than the sketch. A solo author with full control of scope, writing a PRD that explicitly says "a forge scoped to this pipeline — and nothing else — is more valuable than a forge that does everything poorly," looked at the deploy half of his own pipeline and decided it was the "everything" half. The forge halves — graduated review, stacked changesets, pre-receive checks — survived. The half that touches host ports, image storage, process supervision, and rollback under failure did not. If you are rebuilding deploy without a vendor, that is the line an expert drew through his own design, in public, with reasons attached.
Sketched, then cut: the pattern behind the reinvention
The pitch for this shape says it "keeps getting reinvented," and the evidence supports a sharper version: it keeps getting sketched, then cut, and the cutting is the convergence. Follow the three data points in order.
The wish is Mat Duggan's If I Could Make My Own GitHub from April 2026 — written, by its own account, after Ghostty's very public exit from GitHub, discussed across Hacker News and Lobsters. Duggan's complaints became Cave's PRD nearly verbatim: feedback arrives after the push instead of before it, review is boolean when real life lives in the middle, and the forge owns everything around git while git itself does less and less. Cave's PRD cites the essay as a source for its pain points. The wish is real, widely shared, and well specified.
The sketch is Cave's PRD itself: the wish turned into .cave/deploy.toml, a five-step podman sequence, and a FIFO queue. And the cut is the May deferral plus the README's current surface — Implemented forge, Experimental runners, Planned CD. Note what the runners half proves along the way: Cave did build managed self-hosted runners with a pull-based gRPC protocol, Podman-in-Podman execution, and a GitHub-Actions-compatible workflow subset. Checks survived as an experimental feature; deploys did not survive at all. The author kept the half that runs code and cut the half that serves traffic.
The converged shape around it tells the same story from the other direction. Gitea and Forgejo — the tools Cave's persona is migrating from — both kept automation in separate Actions runners (act-based, with docker, host-shell, and LXC executors) rather than folding deploy into the forge binary. Nobody in that lineage ships forge deploy as a primitive; the deploy step lives in a workflow file that shells out to something else. Even GitLab, which did fold everything in, is the PRD's own cautionary tale at 4GB-plus RAM.
Every project that reaches for the forge-plus-deploy shape eventually draws the boundary in the same place: the forge owns code and review, runners own checks, and serving traffic belongs to something with health checks, registries, and a multi-machine story. The reinvention isn't teams failing to learn — it is each team re-deriving where the boundary sits, because the boundary is structural. Deploy is where the second-machine problem lives, and no forge wants to own the second machine.
Decision table: who answers each pipeline need today
Take the PRD's pipeline — push, validate, review, merge, deploy — and ask, for each deploy-side need, what answers it today: Cave's forge, Cave's experimental runners, or a git-push PaaS with buildpacks and a fleet underneath (bex, in this site's running comparison). The table is the deliverable; the paragraphs after it explain the two rows that surprise people.
| Pipeline need | Cave forge (shipped) | Cave runners (experimental) | Git-push PaaS (bex) |
|---|---|---|---|
| Host code, review, merge | Yes — graduated review, stacked changesets, CODEOWNERS | — | No — bring your forge |
| Pre-receive checks, CI | Commit-status API for external CI | Yes — GHA-compatible .cave/workflows/*.yml on self-hosted runners | No — checks run wherever your CI runs |
| Build container from push | No (deferred) | Partial — a workflow can run podman build, but no image lifecycle | Yes — buildpacks from git push, no Containerfile required |
| Image registry + multi-host | No — local podman storage by design | No | Yes — registry plus fleet-wide scheduling |
| Deploy trigger + rollout | No (deferred) | Hook-shaped only — no rollout machinery | Yes — merge/trigger deploys with health checks |
| Zero-downtime + rollback | No — best-effort replacement, prev-image rollback | No | Yes — health-gated rollout, versioned rollback |
| Secrets for workloads | CI secrets only (repo Settings, masked) | Injected into jobs, masked | Yes — per-app env and secret management |
| Second machine | Out of scope — one host, one env per repo | Runners can run remotely, workloads cannot follow | Yes — machines are reconciled declarations |
Two rows deserve more than a cell. First, self-hosted runners are the half where Cave's shape genuinely wins for small teams. A pull-based runner that takes GHA-compatible workflow files, runs them on your own hardware with Podman-in-Podman isolation, and reports status back into the PR's checks panel replaces a CI vendor for exactly the persona the PRD names — and the trust boundary is honest, with privileged denied by default and an image allowlist for repo-supplied jobs. If your team already owns a box, this is the piece worth adopting: checks are hermetic, per-push work that a runner does well. Nothing in this post argues against it.
Second, the build row is where "works" and "answered" diverge. Yes, a Cave workflow can run podman build — any runner can run any shell command, which is precisely the author's deferral note: deploy goes back to external tools and scripts. But a shell command that builds an image is not an image lifecycle: no registry to push to, no garbage collection policy for the rollback images the PRD's own rollback depends on, no promotion between environments, no signature or provenance. The PRD knew this — the registry non-goal and the prune-breaks-rollback failure mode say so explicitly. A git-push PaaS answers the row because buildpacks produce versioned, registry-stored images as the default path, not as a script somebody maintains. The difference between "my runner can build" and "my platform builds" is who owns the lifecycle around the build, and Cave's author declined to own it.
Who should adopt what today
The decision rules fall out of the table:
- Small team on one box, forge-first → adopt Cave for what it ships. Graduated review, stacked changesets, pre-receive checks, and the experimental runners are the live surface; the project is in everyday use by its own account and the scope discipline that cut deploy is a reason to trust the rest. Run deploy through the external tools the author names — Woodpecker, Drone, or a workflow that shells to your existing path — rather than waiting on Planned.
- You want push-to-URL without owning deploy machinery → use a git-push PaaS and let the forge be a forge. The PRD's own success metric — 80% of Containerfile repos deploying through the forge — is exactly the behavior a buildpacks-based flow delivers without a Containerfile at all. Pair Cave (or Forgejo, or GitHub) with a platform whose deploy primitive includes health checks, registries, and rollback, instead of asking the forge to grow them.
- You can already see the second machine → start declarative. Cave's persona ends at one shared production host by design; the single-box ceiling this site has mapped for Coolify, Dokploy, Dokku, and Belune applies unchanged. The day staging must separate from production, or the box fills, "add a node" on a forge-plus-podman stack means provisioning by hand and running two of everything. A Cluster-API-based fleet treats the second machine as one more reconciled object — the gap this site's single-box-to-fleet playbook walks through concretely.
Name the conditions that would change this verdict. If Cave ships its Planned CD with a registry, health-checked rollout, and environment promotion, the "adopt the forge, get deploy elsewhere" rule expires for single-box teams — and should be rewritten as a real comparison. If your workloads genuinely fit one container per repo with best-effort restarts and you rehearse the prune-breaks-rollback failure mode, the deferred design's risks shrink to documented behavior you have accepted. None of those conditions holds by default today, which is why the default stands: the sketch proved git-push is the interface everyone wants, and the cut proved deploy is the half that needs a platform. Cave's author drew that line through his own design. Believe him.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



