Let a dispatched agent release to production without a workstation #73

Closed
opened 2026-08-08 02:04:21 +00:00 by Grandiras · 0 comments
Owner

A dispatched agent can tag a release, but it cannot deploy one.

deploy/deploy.sh runs on a workstation: it needs deploy/.env (which never leaves that machine) and an SSH route to the server's podman socket. An agent running as a Forgejo Actions job has neither. Measured from a job container inside the dind daemon — the host's sshd is unreachable by every route:

from host.containers.internal:22 gateway :22 public IP :22
a container on the host's podman reachable unreachable unreachable
a job container inside dind unreachable unreachable

So the last step of a release needs the same inversion the dev stack already uses: the host watches and acts, rather than being pushed to.

The dev stack follows :main. Production can follow :latest on exactly the same mechanism, because :latest moves only on a version tag and never on a merge — that invariant already exists. Pushing vX.Y.Z then becomes the whole release, doable from a phone.

Two things this has to get right:

  • Pinning must stand the watcher down. deploy.sh vX.Y.Z has to leave production on that version, or an emergency rollback from a workstation gets undone by the timer a minute later.
  • Production's env file has to live on the server for the host to be able to recreate anything unattended. That is a real change to the "nothing is copied to the server" property and needs to be a deliberate, documented trade rather than a side effect.

Also: correct the reachability claim in deploy/README.md, CLAUDE.md and update.sh. It currently reads as "no route exists", which is too broad and invites someone to "fix" it with host.containers.internal.

A dispatched agent can tag a release, but it cannot deploy one. `deploy/deploy.sh` runs on a workstation: it needs `deploy/.env` (which never leaves that machine) and an SSH route to the server's podman socket. An agent running as a Forgejo Actions job has neither. Measured from a job container inside the dind daemon — the host's sshd is unreachable by every route: | from | `host.containers.internal:22` | gateway `:22` | public IP `:22` | |---|---|---|---| | a container on the host's podman | reachable | unreachable | unreachable | | **a job container inside dind** | **unreachable** | — | **unreachable** | So the last step of a release needs the same inversion the dev stack already uses: the host watches and acts, rather than being pushed to. The dev stack follows `:main`. Production can follow `:latest` on exactly the same mechanism, because `:latest` moves *only* on a version tag and never on a merge — that invariant already exists. Pushing `vX.Y.Z` then becomes the whole release, doable from a phone. Two things this has to get right: - **Pinning must stand the watcher down.** `deploy.sh vX.Y.Z` has to leave production on that version, or an emergency rollback from a workstation gets undone by the timer a minute later. - **Production's env file has to live on the server** for the host to be able to recreate anything unattended. That is a real change to the "nothing is copied to the server" property and needs to be a deliberate, documented trade rather than a side effect. Also: correct the reachability claim in `deploy/README.md`, `CLAUDE.md` and `update.sh`. It currently reads as "no route exists", which is too broad and invites someone to "fix" it with `host.containers.internal`.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente#73
No description provided.