Deploys without credential custody: wiring client sites to git
This month we launched a new brand site on Cloudflare Pages. The launch went fine. The interesting problem showed up the moment it was live: how do deploys two through two hundred happen, when our security rule says nobody gets to keep the token?
The custody rule
We run a small healthcare software company, which means we treat credentials the way a pharmacy treats controlled substances: signed out for a purpose, accounted for, and destroyed when the purpose ends. Any token minted for a one-off job is revoked the moment the job verifies. No exceptions for convenience, because convenience is exactly how a scoped token from March ends up in a chat log in August.
For the launch, that worked cleanly. We minted a narrowly scoped API token, pushed the site as a direct upload, attached the custom domain, verified it live, and destroyed the token the same day. Textbook.
And then we'd built ourselves a trap. The next content edit needs a deploy. A standing token in someone's password manager violates the custody rule. Minting a fresh token for every one-line CSS change survives exactly until the day it's annoying, and then someone keeps one "just for this week."
The fix: make git the credential
The answer we settled on, and now use as our standard for every static client site, is to remove humans from the deploy path entirely:
- The site's canonical build lives in a git repository owned by an entity account, not a personal one. The brand's own account owns the brand's repo, so an employee or contractor leaving never orphans the deploy chain.
- The hosting project is connected to that repo. Cloudflare Pages (Netlify and Vercel work the same way) watches the branch and deploys every push. Nobody holds a hosting token at all, for anything, ever again.
- The build machine pushes with a deploy key: a single-purpose ed25519 keypair added to that one repository, with the private half generated on the build box and never leaving it. An SSH host alias with
IdentitiesOnly yespins the key so it can't even be offered to any other host or repo.
Now "who can deploy" has the same answer as "who can push," which is auditable, revocable per key, and visible in the repo's settings page. Revoking access is deleting one key. There's no token to rotate because there's no token.
The gotcha that costs a domain move
A direct-upload Pages project cannot be converted to a git-connected one. Cloudflare makes you create a new project, verify it on its preview URL, then move the custom domain over and delete the old project. It's a clean migration, but it's an extra session you can skip entirely by connecting git on day one, even if the repo only has one commit in it.
Two smaller ones worth knowing:
- Verify content at the origin, not through the proxy. If you checksum a deployed page through Cloudflare's proxy, features like email obfuscation rewrite the HTML on every fetch and your hashes will never match. Verify against the
*.pages.devorigin URL, then check the proxied domain separately for the things the proxy is supposed to do. - Decide repo ownership before you create it. Moving a repo between accounts later can break the webhooks and host integrations that pointed at it. Ten minutes deciding "which entity owns this" saves the churn.
Why a small shop should care most
Big companies have secret managers, rotation policies, and someone whose job is to care. Small shops have a founder's laptop and good intentions, which is exactly the environment where a long-lived token quietly becomes infrastructure. The git-wired pattern isn't just tidier security, it's less work: one setup session per site, and every deploy after that is a git push someone was going to do anyway.
Credentials you don't hold can't leak. It's the cheapest security control we know of, and it made our client-site deploys simpler, not harder.
More Facets Novum Insights · Facets Novum LLC · facetsnovum.com
