Deploy reviewed runtime changes to exact existing VPN nodes through the canonical dry-run and deployment transaction. Use after provisioning and management enrollment; fresh-node setup uses vpn-bootstrap, evidence-only checks use vpn-acceptance.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Is that skill safe to install?
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
Are you the author of Vpn Deploy?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/po4yka-vpn-deploy)
---
name: vpn-deploy
description: Deploy reviewed runtime changes to exact existing VPN nodes through the canonical dry-run and deployment transaction. Use after provisioning and management enrollment; fresh-node setup uses vpn-bootstrap, evidence-only checks use vpn-acceptance.
---
# Deploy exact VPN nodes
Use [the ordinary re-deploy recipe](../../../docs/how-to/deploy.md#ordinary-re-deploy-recipe) as the command and input contract.
## Freeze scope and inputs
After an interruption or checkout change, first follow [continuation](../../../docs/how-to/operator-maintenance.md#continue-after-an-interruption). Reconcile pending transactions before retrying.
1. Run root workspace discovery and resolve the selected task/change, clean immutable source, deployable digest, exact inventory aliases, provider/environment and changed layer. Preserve prior authorization within its resource, action, cost and time bounds; inspect current identities and capabilities at action time. `ENV` selects a Terraform workspace, not Ansible host scope.
2. Check the existing recovery generation, confirmed Tailnet identity, completed SSH ownership migration and fresh dual-path contexts. Route missing first-enrollment state to `vpn-bootstrap` within existing authority. Ordinary deploy validates installed state; it cannot invent enrollment or repair missing receipts.
3. Prepare the selected private runtime secrets and all three exact-alias mappings: `DEPLOY_SSH_CONTEXTS_FILE`, `DEPLOY_PROMOTION_CONFIG_FILE`, and `DEPLOY_SSH_BASELINE_FAILURE_RECEIPTS_FILE`. Use fresh baseline-failure output sinks. Clear enrollment credentials. Keep plaintext, keys, state and token-bearing output out of the transcript; preserve unique device identities.
4. For first disposable staging onboarding, use [the intent workflow](../../../docs/reference/protocol-liveness.md#first-onboarding-during-a-disposable-staging-deployment) prepared by `vpn-bootstrap`. An ordinary successful `dry-run` neither validates that onboarding capability nor supplies the required protocol proof.
## Execute the transaction
Use the runbook's fail-fast shell grouping and EXIT plaintext-cleanup trap for any copyable or executed multi-command sequence.
1. Run the affected local gates and the runbook's `make decrypt` / `make validate` preflight, then `make dry-run ANSIBLE_LIMIT='<exact-alias>' ANSIBLE_TAGS=`. Review all changes before mutation. The explicit empty tags retain the full graph; tagged deploy has no matching tagged dry-run contract.
2. A failed preflight or unexpected change stops `deploy`. Diagnose and fix the source or required input, rerun the relevant checks, and obtain a new decision only if the solution exceeds authority. Do not convert an incomplete preflight into a pass by filtering roles or weakening recovery/security gates. The single exception is a dry-run that stops with the `interrupted-protected-ingress-transaction` category (a previous deploy was killed after protected ingress capture) or the `interrupted-nginx-publication` category (an ordinary nginx publication was interrupted and only its pending journal remains). Review every change the refused dry-run reported before the refusal (baseline, package updates, firewall, intrusion prevention, geodata, tailnet management) and confirm no other deploy targets the node. For `interrupted-nginx-publication`, first require `make source-drift ANSIBLE_LIMIT='<exact-alias>'` to pass with the secrets last deployed, because that recovery converges the complete graph with no preview of its protected and later changes; on drift, check out the revision it reports as deployed for this recovery, confirm source-drift passes there, rerun `make dry-run ANSIBLE_LIMIT='<exact-alias>' ANSIBLE_TAGS=` at that revision and continue only if it refuses with `interrupted-nginx-publication`, which shows that revision already contains the pending-journal recovery, then review the pre-block changes it reports instead of the earlier ones, which came from the newer revision. A revision from before that recovery fails with the older helper's generic refusal and cannot recover the journal, so do not deploy there: escalate to the owner, who either recovers the journal by hand or approves reviewing the deployable difference between the deployed and current revisions followed by the recovery from the current revision. Then run `make deploy ANSIBLE_LIMIT='<exact-alias>' ANSIBLE_TAGS=` once; the empty tags matter because an operator `.fleet.mk` may select tags that skip the protected block. It mutates the node (ordinary pre-block roles run first). The first category compensates the accepted ingress and egress generation and ends non-zero by design; the second recovers the recognized journal and then converges the complete graph. Afterwards repeat the full preflight and deploy sequence, from the new revision when there is one. A malformed site journal together with an open egress route makes the rescue fail closed with the journal retained and the protected transports stopped until manual recovery; an unreadable egress status, a refused helper admission (`consumer-helper-authority-refused`) or a missing egress helper whose route state (`outer.json`, or a state directory that is not a root-owned 0700 directory) is present or cannot be judged (`egress-outer-helper-missing`; restore the complete set of root-owned `transport_runtime_*.py` helpers in `/usr/local/libexec/ripdpi-transport-egress` from the deployed revision, each a root-owned regular file with one link, not group- or world-writable, under root-owned directories that are not group- or world-writable, as the next deploy's admission requires, or recreate the node) also fails closed before adoption with the journal retained. Any other failed preflight keeps this rule.
3. After successful review, run `make deploy ANSIBLE_LIMIT='<exact-alias>' ANSIBLE_TAGS=`, then `make verify ANSIBLE_LIMIT='<exact-alias>'` and the applicable source-parity check. The controller owns readiness snapshots, serial exact-node writes, runtime transactions and promotion proofs. Let a failed or interrupted transaction reconcile its existing generation; retain diagnostic/authority artifacts rather than deleting them for a retry.
4. Route live acceptance to `vpn-acceptance`. Finish `make clean` for the configured plaintext even when preflight or deployment fails. Disposable provider/executor/identity retirement follows `vpn-cleanup`; plaintext cleanup alone does not finish that lifecycle.
If Terraform changes are included, use [the Terraform procedure](../../../docs/how-to/deploy.md#re-deploy-after-a-terraform-change-instance-type-zone-firewall) and review its saved plan before apply. Replacement follows the authorized recreation path. For same-node disposable firewall changes, retain acceptance on both sides and reissue the registered cleanup manifest through its canonical verb before further guest writes or destruction.
If monitoring changes are included, read [current observability operations](../../../docs/reference/observability-operations.md#current-contract-and-task-history) first. Use the selected component's exact-host lifecycle and resource admission; the retired dedicated-host bootstrap and old receipts do not authorize the co-hosted rollout.
## Completion
Report the exact source revision/digest and targets, observed preflight/deploy/verify/parity results, acceptance outcome and cleanup residuals. A diagnostic baseline-failure receipt proves that failure path only. Local CI, guest verification and protocol/client acceptance remain separate claims; unchanged runtime digests do not refresh old live evidence. Documentation-only changes use documentation/CI gates without redeploying runtime merely to advance a source SHA.