Profiles & defaults
Which credential a command uses and what its paths are relative to — the config file, the resolution order, and the difference between a profile and a project.
Commands on this page: config
Before any command touches the network, two things have already been decided: which stored
credential it authenticates with, and what a short path is relative to. Both are settings you
control, both are stored per profile in ~/.pulsvault/config, and both are managed by one command —
pulsvault config.
Getting them right is what turns this:
pulsvault get acme-web/prod/product/web/db/passwordinto this:
pulsvault get prod/product/web/db/passwordProfile or project?#
They look alike on a command line and are nothing alike underneath, which makes this the easiest mistake to make:
| What it is | How you choose it | |
|---|---|---|
| Profile | a stored credential — an API key plus the deployment it belongs to | --profile, PULSVAULT_PROFILE, config use |
| Project | the first segment of a secret's path | --project, PULSVAULT_PROJECT, config project |
Selecting a profile never changes which project you address, and setting a project never changes which
key you use. So pulsvault config use acme-web does not make pulsvault list prod/api work — that
path still begins with a project name, and the CLI will look for a project called prod.
Confusing the two produces a specific error, which names every profile you do have:
$ pulsvault --profile acme-web whoami
error: profile "acme-web" has no host — configured profiles are: "mycorp", "prod"
· a PROFILE is a stored credential (an API key + its deployment)
· a PROJECT is the first segment of a path: `<project>/<env>/<namespace>/<secret>`
· to address a project, keep your profile and name it in the path, or pass --project acme-web
· to create it, run `pulsvault login --profile acme-web --host URL`A profile the CLI does not recognise — a typo like --profile prd, or one you never gave a --host —
errors out instead of resolving to production. That is deliberate: inheriting the production host on a
typo is how a "staging" command writes a live value.
Choosing which credential a command uses#
config list is the inventory. It marks the active profile, resolves each host, and says which
profiles are unusable and why:
$ pulsvault config list
* prod https://api.pulsvault.com/api/v1
ci-deploy https://api.pulsvault.com/api/v1 (no token — run `pulsvault login --profile ci-deploy`)
mycorp https://vault.mycorp.com/api/v1
* = active profile (used when --profile / PULSVAULT_PROFILE is absent)
/home/you/.pulsvault/configA profile keeps its host after pulsvault logout, so appearing in this list says nothing about being
usable — the (no token — …) marker is the part that matters. This is where to look after a command
fails with no API token.
The rest of the command surface:
pulsvault config # same as `config list`
pulsvault config use ci-deploy # make it the active profile
pulsvault config remove old-ci # forget a profile's token, host and project
pulsvault config rm old-ci # `rm` is accepted for `remove`
pulsvault config path # print the config file's location and nothing elseconfig use refuses a profile that holds no token, because making an empty profile active would break
every later command with a puzzling error rather than this one:
$ pulsvault config use ci-deploy
error: profile "ci-deploy" has no stored credentials — run `pulsvault login --profile ci-deploy`Three ways to pick a profile, in increasing order of how long the choice lasts:
pulsvault --profile ci-deploy whoami # this command only
PULSVAULT_PROFILE=ci-deploy pulsvault list acme-web/prod/api # this shell, or this CI job
pulsvault config use ci-deploy # until you change it againResolution is --profile → PULSVAULT_PROFILE → the config's active profile → prod. Global flags
work on either side of the command name, so pulsvault --profile mycorp whoami and
pulsvault whoami --profile mycorp are the same invocation.
Every command except config itself honours PULSVAULT_PROFILE. The config subcommands do not:
config project looks at --profile and then the stored active profile, and config list, use and
remove look only at the stored active profile.
So PULSVAULT_PROFILE=mycorp pulsvault config project acme-web writes the project onto whichever
profile is active, not onto mycorp. Name it explicitly instead:
pulsvault config project --profile mycorp mycorp-appSetting a default project#
Every path is {project}/{env}/{namespace}/{secret} — the scheme is explained in
Paths, plans & roles. Store a project and you may drop that leading
segment:
$ pulsvault config project acme-web
default project for profile "prod" is now "acme-web"
paths may now omit it: `pulsvault list <env>/<namespace>` addresses acme-web/…It is stored per profile, because a key issued by one deployment usually addresses one project there — someone holding keys for two deployments wants a different default in each, not one that follows them across. All three forms:
$ pulsvault config project # what is it currently?
acme-web (profile "prod")
$ pulsvault config project - # clear it
cleared the default project for profile "prod"
$ pulsvault config project
no default project for profile "prod" — paths must name one: <project>/<env>/<namespace>/<secret>A project is one path segment, and passing more than one is refused rather than silently stored:
$ pulsvault config project acme-web/prod
error: a project is a single path segment, got "acme-web/prod" — pass just the project, e.g. `pulsvault config project acme-web`What the prefix actually does#
The rule is deliberately simple, and it never guesses: a path is left alone if it already starts
with your configured project; otherwise the project is prepended. With acme-web stored, all four
of these address the same secret:
pulsvault get prod/product/web/db/password # prefixed
pulsvault get acme-web/prod/product/web/db/password # already qualified, untouched
pulsvault --project acme-web get prod/product/web/db/password # flag wins over the stored value
PULSVAULT_PROJECT=acme-web pulsvault get prod/product/web/db/passwordThe CLI does not try to work out whether your first segment is a project or an environment. That would depend on server state, and guessing wrong means reading the wrong project's secret.
The check is against the one project you configured, so a path naming a different project is
prefixed like anything else — with acme-web stored, other-team/prod/api resolves to
acme-web/other-team/prod/api, not to other-team. To reach another project, either clear the
default, override it for one command with --project other-team, or write the path out in full from a
profile that has no project set.
With no project configured anywhere, paths must be fully qualified, and a short one fails immediately:
error: expected <project>/<env>: pass a fully-qualified path, or set --project / PULSVAULT_PROJECTWhat the config file holds#
~/.pulsvault/config is a flat key=value text file — no sections, no quoting, comment lines start
with #. pulsvault config path prints its location, which is the only reliable way to find it on a
machine where $HOME is unusual:
pulsvault config pathA machine with three profiles looks like this:
# PulsVault CLI config
profile=prod
profile.ci-deploy.host=https://api.pulsvault.com/api/v1
profile.ci-deploy.token=pv_live_…
profile.mycorp.host=https://vault.mycorp.com/api/v1
profile.mycorp.project=mycorp-app
profile.mycorp.token=pv_live_…
profile.prod.host=https://api.pulsvault.com/api/v1
profile.prod.project=acme-web
profile.prod.token=pv_live_…Four keys exist, and nothing else is written:
| Key | Holds | Written by |
|---|---|---|
profile |
the active profile — which one commands use when none is named | config use, and the first-ever login |
profile.<name>.token |
that profile's API key, in plain text | login |
profile.<name>.host |
that profile's API base, including /api/v1 |
login |
profile.<name>.project |
that profile's default project | config project |
Two consequences of it being a plain file the CLI rewrites whole:
- Editing it by hand works, but comments do not survive. Every write serializes the whole file
from scratch, so anything you add beyond the four keys is dropped the next time
loginorconfigruns. Prefer the commands. - Only
logincreates the directory.configandlogoutwrite into~/.pulsvault/but never create it, soconfig projecton a machine that has never logged in fails with a filesystem error. Runpulsvault loginfirst.
login writes it readable only by your user account. If the permissions are ever loosened, every
command warns until you fix it:
warning: /home/you/.pulsvault/config is mode 0644 and holds an API key in plain text — run `chmod 600 /home/you/.pulsvault/config`Any command that writes the file also repairs it — login, logout and config replace it with a
fresh 0600 file rather than truncating the loose one in place. The warning is therefore about the
window before that next write, which is exactly when a read-only command like get is exposing the key.
Secret values never touch this file, or any file — only the API key does. See Authenticating for what that key can and cannot reach.
Resolution, all four settings#
Each setting resolves independently, first match wins:
| Setting | 1st | 2nd | 3rd | 4th |
|---|---|---|---|---|
| profile | --profile |
PULSVAULT_PROFILE |
profile= in the config |
prod |
| token | --token |
PULSVAULT_TOKEN |
profile.<p>.token |
— fails with no API token |
| host | --host |
PULSVAULT_HOST |
profile.<p>.host |
the built-in host for a known profile name |
| project | --project |
PULSVAULT_PROJECT |
profile.<p>.project |
— paths must be fully qualified |
The profile resolves first, and the other three key off it. --profile mycorp therefore changes
which token, host and project are read out of the file — while an explicit --token, --host or
--project overrides whatever that profile stored.
The host is also validated before the token, so a mistyped profile name reports that rather than complaining about a missing credential.
A stored key stays with its host. Overriding the host with --host or PULSVAULT_HOST needs a
key given alongside it, through --token or PULSVAULT_TOKEN. On its own, the override is refused
rather than sending the profile's stored key to a server it was never verified against:
error: refusing to send profile "prod"'s stored key to https://vault.example.com/api/v1 — it belongs to https://api.pulsvault.com/api/v1
· pass the key for that host with PULSVAULT_TOKEN (or --token)
· or store one for it: pulsvault login --profile <name> --host https://vault.example.com/api/v1A stray PULSVAULT_HOST left in a .envrc or a CI setting used to be all it took to hand a
production key to another server. Naming the profile's own host is not an override and is accepted.
The host must be https://. Plain http:// is refused for anything but the machine you are on,
because every request carries the key as a bearer token and http would put it on the network in
clear text. A redirect from https to http is refused for the same reason.
In CI: no config file at all#
Skip login entirely and set environment variables. Nothing is written to disk, which is what you
want on a runner whose filesystem outlives the job:
# PULSVAULT_TOKEN comes from a masked CI variable — the runner already has it in its environment
export PULSVAULT_PROJECT=acme-web
pulsvault get prod/product/web/db/passwordName the CI variable PULSVAULT_TOKEN and the CLI reads it with no shell at all;
Using in CI has the per-platform setup for GitLab and GitHub.
These four are the complete set — the CLI reads no other environment variables:
| Variable | Replaces |
|---|---|
PULSVAULT_TOKEN |
the stored API key |
PULSVAULT_HOST |
the profile's API base |
PULSVAULT_PROFILE |
which stored profile to use |
PULSVAULT_PROJECT |
the stored default project |
--insecure, -q and -o are flags only; there is no environment variable for any of them.
A profile is convenient on a laptop, where you set it once and forget it. In CI the opposite is true:
PULSVAULT_TOKEN plus PULSVAULT_PROJECT makes the job's credential and scope visible in the job
definition, leaves nothing behind on the runner, and cannot drift from a config file written by some
earlier build.
Related#
- Authenticating — getting a key onto the machine, and checking it still works
- Paths, plans & roles — the canonical
{project}/{env}/{namespace}/{secret}path - Command reference — every flag, generated from the binary