ProficientNowTechRFCs

7. Deploy Action

RFC-PAM-0002                                                    Section 7
Category: Standards Track                                   Deploy Action

7. Deploy Action

← Previous: 6. Authorization Model | Index | Next: 8. Group Synchronisation →


7.1 Why a Wrapped Action Exists

Deploying on the compose-based hosts means running docker compose inside /srv/pnats/<project>. The Docker daemon runs as root; anyone who can talk to it can ask it to do anything root can — mount / into a container and chroot into it, read any file, append to sudoers. Three one-line commands, no exploit. So "deploy but cannot write" is impossible while a person can reach Docker (Invariant 4).

pn-deploy lets a person trigger Docker to do a specific, pre-approved thing without ever talking to Docker. It is the only sudo rule Deployers and Editors possess, and it runs as the deploy service account — the sole member of the docker group.

7.2 Verbs and Who May Use Them

The wrapper mirrors the compose verbs engineers use; the arguments are validated rather than the verbs restricted.

VerbDeployerEditorNotes
up [service…] [--build] [--ref TAG]✅ (--build only where the project allows it)✅pull/build then bring up
restart [service…]✅✅
stop [service…]✅✅
pull / build [service…]✅✅
logs [service] [-f] / ps✅✅
rollback✅✅to the previous image reference kept per project
down❌✅ (confirmation on prod)removes the stack — an outage
exec <service> <cmd>❌✅ (I/O-logged)a shell inside a container may be root inside it

7.3 Validation — What Is Checked, Not Trusted

InputCheck
Projectmust exist in the root-owned allow-list (/etc/pn-deploy/projects): project → compose path → permitted verbs and options
Service namesmust appear in the project's own compose configuration
--refmust be a well-formed tag or digest
Anything elserefused. There is no argument pass-through to Docker — no -v /:/host, no --privileged, ever
Concurrencya per-project lock; the compose project name is already pinned, so no duplicate stacks
Environmentscrubbed; the wrapper's own configuration is root-owned and Ansible-managed, so an Operator cannot widen the allow-list — only a PR can

7.4 Evidence

Every invocation records who (Entra UPN via SUDO_USER), project, verb, services, ref, and result to the journal → Sentinel, and posts a one-line note to #infra (the existing convention: "drop a note in #infra if you change shared state").

7.5 Developer Experience

Nobody should have to read this RFC to deploy. The design ships with:

ElementPurpose
dc alias, available inside /srv/pnats/<project>maps the familiar docker compose … shape onto pn-deploy for the current project
pn-deploy --helpexplains every verb and why a refused thing is refused
Companion quickstart in ovh-infra (docs/pn-deploy-quickstart.md)plain-language guide in the style of arc-ssh-quickstart.md: old command → new command, common errors, who to ask
TodayAfter
cd /srv/pnats/pnow-ats-v2 && docker compose up -d --build apicd /srv/pnats/pnow-ats-v2 && dc up -d --build api
docker compose logs -f workerdc logs -f worker
docker compose restart apidc restart api

No password prompt; same result; attributed to the person.

7.6 The Escape Hatch

For the small fraction of work the wrapper does not cover (a one-off docker run, inspecting the socket, anything exotic): activate PIM Admin once (≤4h, MFA + reason) and use raw docker as root for the window. Routine work needs no elevation; unusual work needs one activation. Deployers have no escape hatch by design.

7.7 Failure Modes

FailureBehaviour
Wrapper missing or misconfigureddeploys fail closed; no fallback to raw Docker for non-admins
Docker downerror surfaced to the caller
Lock heldwait/refuse with a clear message
Wrapper defect blocks a deployEditor uses the escape hatch; wrapper fixed by PR

7.8 Scope

Applies to the compose-based classes only — uat, prod, erp. Kubernetes deploys remain GitOps (ArgoCD); no human deploys on k8s or pve hosts by hand, so those classes have no deploy action.


Document Navigation


End of Section 7

On this page