blobhub or blobhub-worker when nobody is sitting at a terminal to type it in.
Authentication and Authentication and Profiles document the mechanism — a
literal flag, its own -stdin sibling, or a named -env <VAR> sibling, resolved before either binary even
considers prompting. This page is worked examples: the same sources, wired into the infrastructure that
actually runs BlobHub commands.
Two things apply to everything below, so they’re stated once instead of five times:
- Provision a service account’s key, not a person’s. Every recipe here hands
a credential to infrastructure nobody is watching in real time. A service account key means the Blobs,
revisions and thread items that infrastructure produces are authored by, say,
ci-deploy— not by whoever happened to set the pipeline up — and revoking it later never touches a person’s own access. - Lead with
--api-key-stdin(or--api-key-env), never the literal--api-key. The literal flag sits inpsoutput for as long as the process runs and in shell history for as long as that file exists — exactly the two places a provisioning script tends to run. Every example below pipes or points at the key instead. The same three-source shape covers--api-urltoo, for anything not pointed at the defaulthttps://api.blobhub.io/v1— it’s left out of the examples below to keep them focused on the key.
When not to use login at all
An environment key resolves on its own, with no credentials file, no profile, and nothing to clean up:
login earns its place over that one line when you need a stored profile, for three reasons that a bare
environment variable can’t give you:
- A name.
--profile ciis something a teammate can read in a service definition; a key sitting in one process’s environment isn’t addressable at all once that process is gone. - A key validated before anything depends on it.
logincallsGET /v1/users/meand fails loudly, before any deploy or any turn runs, if the key is wrong. An unvalidated environment variable fails at the first real command instead — later, and with a less specific error. - For the worker specifically, a state tree that survives rotation. A state tree
is named after where its key came from: a stored profile’s tree is named after the profile, so
rotating the key under
--profile prodkeeps the same tree — same cursors, same lock. A key supplied directly by--api-key/BLOBHUB_API_KEYgets a tree named after a digest of that key, so rotating it is, from the worker’s point of view, moving to a brand new tree with no memory of the old one. Nothing breaks, but every cursor starts over.
login, and why the CI recipe —
the one genuinely short-lived case — is the one place a bare environment variable is usually the right answer
for either binary.
CI
A CI job’s secret store hands the runner an environment variable, already masked in logs and gone the moment the job’s container or VM is torn down. For ablobhub command that runs once and exits, that’s the entire
setup, on any CI platform:
login against the variable the platform already
injected, rather than re-typing the key anywhere:
Secret, a Docker secret, or an Ansible vault as one step of the same pipeline.
One shape, three paths
Docker secrets, a Kubernetes mountedSecret, and systemd’s LoadCredential= all exist for the same reason:
hand a process a file only it can read, instead of an environment variable that shows up in docker inspect, /proc/<pid>/environ, or a child process’s own environment. Whichever platform delivers it, the
command on the receiving end is identical — pipe the file into --api-key-stdin:
Docker and Compose secrets
Adocker run -e or a Compose environment: entry is visible to docker inspect and to anything that can
docker exec into the container — indefinitely, for as long as the container runs. Docker secrets exist
specifically to avoid that: they’re mounted as files under /run/secrets/<name>, readable only inside the
container that declared them, never inspectable from outside it.
Creating the secret (Swarm) or declaring it (Compose) both land the key at the same path:
exec in the entrypoint matters: without it, start would run as a child of the shell instead of as the
container’s own process, and a docker stop would have to wait out its default grace period instead of
reaching the worker’s own signal handling directly.
Kubernetes
The same reasoning as Docker applies, with one more wrinkle: an environment variable sourced from asecretKeyRef is set for the life of the container and readable via that container’s own /proc/1/environ
by anything sharing its process namespace. A volume-mounted Secret can be permissioned tighter and doesn’t
widen that surface. Project the key to one file and mount it read-only:
Secret updates in place when the underlying object changes, but that only matters to a process
that re-reads it; a one-shot login at container start won’t notice a live rotation. Roll the pod (or run
login again) after rotating the key, the same as you would for any other credential baked in at startup.
systemd’s LoadCredential=
An environment variable set in a unit file is visible to systemctl show and to /proc/<pid>/environ for
any local user who can read that process, and it’s inherited by every child process the unit spawns.
LoadCredential= is systemd’s own answer, built for exactly this: it stages the credential in a per-unit,
mode-0400 directory owned by the service’s own user, exposed only to that unit via $CREDENTIALS_DIRECTORY
— nowhere else, and not to unrelated processes on the same host.
$CREDENTIALS_DIRECTORY is set for every Exec*= line in the unit, so ExecStartPre can read it and
ExecStart never needs to. ExecStart=/ExecStartPre= are not run through a shell by default — that’s why
the redirect is wrapped in sh -c '...' rather than written directly on the ExecStartPre= line, where
systemd would pass the literal < to the binary as an argument instead of interpreting it.
Ansible
A playbook is often the thing populating the file the three recipes above expect, but it doesn’t have to write the key to disk on the target at all:ansible.builtin.command’s stdin parameter feeds a string
straight to the child process’s standard input, so the raw key never touches the target’s filesystem.
no_log: true is not optional here. Once the key is a module parameter rather than a file path, Ansible’s
own verbose and failure output is the next place it could leak — no_log is what keeps it out of both. Keep
blobhub_api_key itself in an ansible-vault-encrypted variable file; nothing about login cares where the
value came from before it reached stdin.
Keep
--profile a literal or a variable you know is always set. --profile "{{ some_var }}" rendering to
an empty string is refused outright on the worker (PROFILE_NOT_FOUND) rather than falling through to
whatever the stored default happens to be — see the
warning in Authentication and Profiles for why that’s
deliberate. blobhub login on the CLI side does not share this guard and falls through instead, which is
exactly the asymmetry to design around rather than discover in production.Confirming what got stored
Both binaries can report the same thing a script would otherwise have to scrape from human-readable lines — but spelled differently, and shaped differently, which is worth seeing side by side rather than assuming one mirrors the other.--json is a root option on the CLI and a subcommand option on the worker:
schema_version; the worker prints the bare object with no
envelope at all — the same divergence blobhub-worker login has under --json. Neither
ever includes the key itself, here or on any other command. running on the worker’s payload is the one
field worth watching after a deploy: it tells you whether start actually took, not just whether the
credential resolved.
The shapes above are one instance of a wider rule worth carrying into every script built from this page:
key off the error code and the exit status, never the message text. The two binaries word the same
condition differently on purpose — see Error codes — so a check written against a
sentence is one release, on either side, away from breaking.
See also
- Authentication and Authentication and Profiles — the full flag reference these recipes assume: every source, the prompting rules, and what each binary writes.
- Service Accounts — the identity every recipe on this page should be provisioning, and its own end-to-end recipe for minting one.
- Configuration — the credentials file, the precedence ladder, and the
--jsonenvelope in full. - Error codes and Reference —
INPUT_REQUIRED,ENV_VAR_NOT_SET, and every other code a scriptedlogincan raise.

