CI/CD pipelines
Load PulsVault secrets into GitHub Actions and GitLab CI, and gate a deploy on whether those credentials still work.
Two jobs, two integrations. Loading puts secrets into your job as masked environment variables. Gating refuses to deploy with a credential that no longer works. They are separate on purpose — most pipelines want one without the other, and a gate that also rewrites your environment is harder to reason about than two steps that each do one thing.
Both consume the same machine data-plane. Store your API key as an encrypted CI variable
(PULSVAULT_TOKEN) and grant its service account a read policy for the paths you need.
Loading secrets#
GitHub Actions#
- name: Load secrets from PulsVault
uses: PulsVault/load-secrets-action@v2
with:
token: ${{ secrets.PULSVAULT_TOKEN }}
namespace: acme-web/prod/product/web
paths: |
db/password -> DATABASE_PASSWORD
stripe/api-key -> STRIPE_KEYThe action bulk-reads the mapped secrets, masks each value in the logs with ::add-mask::, and
exports them as environment variables for later steps.
GitLab CI#
include:
- remote: 'https://templates.pulsvault.com/gitlab-ci/v2/load-secrets.yml'
deploy:
extends: .pulsvault-load-secrets
variables:
PULSVAULT_NAMESPACE: acme-web/prod/product/web
PULSVAULT_PATHS: |
db/password -> DATABASE_PASSWORD
script:
- ./deploy.sh # DATABASE_PASSWORD is exported for the jobSet PULSVAULT_TOKEN (and optionally PULSVAULT_HOST) as masked CI/CD variables.
Pinning a version#
include: remote: has no version syntax of its own, so the version lives in the path:
| URL | Resolves to |
|---|---|
…/gitlab-ci/v2/load-secrets.yml |
the newest 2.x — use this |
…/gitlab-ci/v2.0.0/load-secrets.yml |
that exact release, forever |
v2 moves as 2.x releases happen, the way @v2 does for a GitHub Action. Pin the full version if
your pipeline must never change under you.
Universal pattern (any CI)#
Anywhere the CLI runs, run injects a whole namespace as env vars around your command:
pulsvault run --namespace acme-web/prod/product/web -- ./deploy.shGating a deploy on credential health#
A secret being present is not the same as it being usable. A rotated password or a revoked token reads back perfectly and breaks the deploy that uses it. Gating closes that gap.
Which check to run#
Two questions, and most pipelines are asking the second one:
| You need | Use | Cost |
|---|---|---|
| Was this known-good recently? | max-age |
Reads the result your scheduled checks already produced. Nothing reaches your provider |
| Does it work right now? | no max-age |
Runs the probe — a real sign-in with your credential |
Reach for max-age by default. PulsVault is already checking these credentials on a schedule you
pay for, so asking "was it green within the hour" is free and truthful. Save the live probe for the
moments where now is the question: just after rotating a credential, or immediately before
promoting a build.
GitHub Actions#
- name: Check credentials
id: check
uses: PulsVault/validate-action@v2
with:
token: ${{ secrets.PULSVAULT_TOKEN }}
path: acme-web/prod/product/web/db
max-age: 1h # omit to probe now
- run: ./deploy.shThe action reports an outcome and fails the step only for the outcomes you name in fail-on:
outcome |
Means | In fail-on by default |
|---|---|---|
pass |
the credential works | — |
failed |
it genuinely failed | yes |
transport |
could not reach or authenticate to PulsVault | yes |
incomplete |
the check could not be completed — a timeout, a probe error, rate-limited | no, warns |
not-configured |
no validation is configured for that path | no, warns |
fail-on: failed,transport,incomplete # strict: stop even when we could not tell
fail-on: failed,transport,not-configured # enforce "production paths must be validated"outcome, result and exit-code are outputs, so a later step can branch on them:
- if: steps.check.outputs.outcome == 'incomplete'
run: echo "Could not verify — deploying on the last known-good result"GitLab CI#
The template uses GitLab's own allow_failure: exit_codes, which is exactly the feature this split
was designed for:
include:
- remote: 'https://templates.pulsvault.com/gitlab-ci/v2/validate.yml'
check-credentials:
extends: .pulsvault-validate
variables:
PULSVAULT_PATH: acme-web/prod/product/web/db
PULSVAULT_MAX_AGE: 1h # omit to probe now
deploy:
needs: [check-credentials]
script:
- ./deploy.shExit 1 (the credential failed) and 5 (could not reach PulsVault) fail the job. Exit 3 (could
not complete the check) and 4 (nothing configured) are allow_failure by default — they show as
warnings and the pipeline continues. For a strict gate:
check-credentials:
extends: .pulsvault-validate
allow_failure: false # 3 and 4 now stop the pipeline tooAny other CI#
The CLI carries the same contract, so a plain script works anywhere:
pulsvault validation-status acme-web/prod/product/web/db --max-age 1h && ./deploy.sh
pulsvault validate acme-web/prod/product/web/db && ./deploy.sh # probe nowThe reason the outcomes are separate is so you never have to. If a timeout is failing your pipeline,
move incomplete out of fail-on (or leave GitLab's allow_failure alone) — that keeps the
warning visible while letting the deploy through. Appending || true throws away the revoked-credential
signal along with the noisy one, and it is the change nobody remembers to undo.
The full exit-code contract is on Validating credentials.
Issue a new key, update the CI variable, verify the pipeline, then revoke the old key — see rotation.
A pipeline with both#
include:
- remote: 'https://templates.pulsvault.com/gitlab-ci/v2/validate.yml'
- remote: 'https://templates.pulsvault.com/gitlab-ci/v2/load-secrets.yml'
check-credentials:
extends: .pulsvault-validate
variables:
PULSVAULT_PATH: acme-web/prod/product/web/db
PULSVAULT_MAX_AGE: 1h
deploy:
needs: [check-credentials]
extends: .pulsvault-load-secrets
variables:
PULSVAULT_NAMESPACE: acme-web/prod/product/web
PULSVAULT_PATHS: |
db/password -> DATABASE_PASSWORD
script:
- ./deploy.shGate first, load second. The gate costs nothing and fails in seconds; loading is the step you only want to reach once you know the credentials behind it are good.
Related#
- pulsvault CLI · Using in CI
- Validating credentials — the exit codes,
--force,--max-age - Credential validation overview — configuring the probes these gates read
- Machine identities — and their API keys