GREEN · RED · BLUE PACKAGE SKILL · VULTR · NEON
Neon Package Skill
A reproducible Green, Red, and Blue workflow for self-hosted Neon — serverless Postgres with storage and compute separated, its layers and WAL durably in Cloudflare R2 — on one guarded, loopback-only Vultr instance.
<profile>/data/ in R2, and the tenant and timeline identities live in colors.yml — so a rebuilt host re-attaches the same identities and rehydrates from R2. Two-phase ownership markers make sure convergence never attaches a foreign or half-initialized prefix.Quick start
npx skills add getcolors/neon
cp .agents/skills/package-neon-green/green ./green
chmod +x green
./green build
./green create --dry-run
Equivalent Red and Blue skills ship red and blue launchers.
Build renders reproducible output and dry-run walks the workflow without provider side effects. Real creation and deletion require explicit authorization.
Architecture
Compute
One Vultr instance and a firewall opening 22 — the only open port. In keygen mode the account SSH key resource is named after the profile and lives in this deployment's state.
Storage tier
Docker Compose: the storage broker, the pageserver, and one safekeeper, every image pinned by tag and digest, all bound to loopback, all writing to R2 under the deployment's own prefix.
Compute node
Postgres under compute_ctl, its roles provisioned from a spec whose SCRAM verifiers are generated on the host — recreate-only by doctrine, disposable by design.
Acceptance
Server-side: the SQL round-trip, three auth refusals, and R2 object evidence. Workstation-side: the SSH tunnel through the generated alias, with and without the password.
Connecting
There is no DNS record and no public database port — the supported client path is the tunnel:
ssh -L 55433:127.0.0.1:55433 <profile>
psql 'postgresql://neon@127.0.0.1:55433/neondb'
The role's password is generated on the server (ssh <profile> cat /etc/neon/secrets/neon_role_password); neon-status summarizes the deployment and neon-rotate rotates the password atomically, with rollback.
The machine keypair
The deployment owns its SSH key, following the workspace SSH Keypair Standard. Leave vultr-ssh-keys out of colors.yml and the first real create generates ~/.ssh/<profile>, registers it at Vultr under the profile name, and a successful delete removes it last — never before.
~/.ssh/<profile> deliberately. A key with no state is never overwritten, and a Vultr key named after the profile that this deployment's state does not own stops the run — if its fingerprint is not yours, do not delete it.Supplying vultr-ssh-keys opts out entirely: the package then generates, validates and deletes no key material. Rotation is a rebuild, because Vultr key lists are ForceNew.
The SSH config block
Convergence also writes one ~/.ssh/config block, following the workspace SSH Config Standard, so ssh <profile> reaches the host with no address, user or -i flag — and the acceptance step rides exactly that alias, so the block is proven, not assumed. Delete removes the block before destroying the instance, the reverse of the keypair: a block that outlives its host is stale but harmless, while a key removed early locks you out of a machine that still exists.
Exposure
| Port | Exposure | Purpose |
|---|---|---|
| 22 | vultr-ssh-sources | Key-only SSH: convergence, recovery, and the client tunnel |
| 55433 / 3080 | loopback | Postgres and the compute API, reached through the tunnel |
| 9898 / 6400 | loopback | Pageserver HTTP and page service |
| 7676 / 5454 / 50051 | loopback | Safekeeper and the storage broker |
Durability, honestly
Layers and WAL survive the host in R2, with an honest bound: commit acknowledgement requires the host's disk, uploads are asynchronous, and a fresh safekeeper cannot serve offloaded WAL back — so a full host loss recovers what the pageserver had uploaded, and the single-node RPO is the activity since its last checkpoint upload. A wiped storage tier on a surviving host loses nothing; both cases were rehearsed against the live deployment. Never configure R2 lifecycle rules on the bucket; they would delete live layers and WAL.
Safety
- Credentials are
COLORS_PAR_*variables in the gitignored.envrc.private— never incolors.yml. - Never export
COLORS_PAR_PROFILE; the profile keys remote state. .colors/is generated output: never edit or commit it.compute-prevent-destroy: trueguards deletion behind a one-run override — and delete leaves the R2 data in place.