Machine Keys (Non-interactive)
By default, ov run prompts for your master password every time it runs. That’s fine for local development but blocks automated deployments — CI pipelines, Docker containers, and systemd services have no terminal to type into.
Machine keys solve this. You derive a scoped keyring file once (interactively, as a human), then automated processes read its derived key material without prompting. The command writes key material directly to the destination file; stdout contains only confirmation text.
How it works
Section titled “How it works”ov auth derive-machine-key → writes a scoped v3 keyring to a chmod 600 fileov run --machine-key <path> → loads the keyring and runs without promptingThe v3 file is an OVKR container containing derived key material, its scope descriptor, and the public key used to verify accepted egress policies. It contains neither your master password nor a raw KEK. ov run validates permissions before reading: if the file isn’t exactly chmod 600 and owned by the current user, it hard-errors and refuses to load the key.
v0.20 refuses v2 machine-key files, even before egress policy adoption. There is no password-free conversion. Record the original explicit scopes without inspecting or pasting the old file, then re-derive with the v0.20 binary:
ov auth derive-machine-key --scope myapp/production --out /etc/ov/machine-v3.keyRepeat --scope app/env for the deployment’s original authorized selectors. Omit --scope only when you intentionally want wildcard access; omission means wildcard, not the configured app or the old file’s scope. Replace the configured path, restart the daemon or service, and restart its ov mcp serve processes so those processes use the new binary and keyring.
-
Log in and create your app
If you haven’t already:
Terminal window ov auth loginov app create myappov secret set DATABASE_URL --app myapp -
Derive the machine key
Run this once as a human — it prompts for your master password and writes the derived key to disk:
Terminal window ov auth derive-machine-key --scope myapp/production --out /etc/ov/machine.kekThe keyring contains only the recorded scope’s derived material. Add repeated
--scopeselectors when the deployment needs more than one partition. Bind requests at call time via--app <slug>and--env <slug>onov runandov agent start; those routing flags do not widen the keyring.You’ll see:
Master password: ▌Machine keyring written to /etc/ov/machine.kekThis file is BEARER MATERIAL for its scope.- Keep it chmod 600, owned by the service account- Never commit to version control- Revoke access by rotating the OV API key for this deployment- Rotate with: ov auth derive-machine-key --scope myapp/production --out /etc/ov/machine.kek --forceVerify the permissions:
Terminal window ls -la /etc/ov/machine.kek# -rw------- 1 deploy deploy ... /etc/ov/machine.kek -
Test non-interactive injection
Terminal window ov run --machine-key /etc/ov/machine.kek --app myapp -- env | grep DATABASE_URL# DATABASE_URL=postgres://...No password prompt. If it doesn’t work, see Troubleshooting.
Using in a deploy script
Section titled “Using in a deploy script”#!/usr/bin/env bashset -euo pipefail
ov run \ --machine-key /etc/ov/machine.kek \ --app myapp \ --secrets DATABASE_URL,REDIS_URL \ -- ./bin/serverThe process starts with DATABASE_URL and REDIS_URL in its environment. OpaqueVault injects them without writing the environment to disk or placing values in command arguments. Child-process output remains application-controlled and can log injected values.
Docker
Section titled “Docker”Download the Linux v0.20.0 archive for the image’s architecture, verify its
signed checksum by following Release signing & verification,
and extract ov to ./bin/ov in the build context. Derive the v3 machine key
with v0.20.0 or newer; v0.19 and older read only v2 keys and reject a v3
keyring, so pin the image to v0.20.0 or newer.
Store the machine key on the host and bind-mount it read-only:
# Dockerfile — no secrets baked inFROM debian:bookworm-slimCOPY --chmod=0755 ./bin/ov /usr/local/bin/ovRUN ov --version | grep -F '0.20.0'COPY ./bin/server /app/serverENTRYPOINT ["ov", "run", "--machine-key", "/run/secrets/machine.kek", "--app", "myapp", "--", "/app/server"]docker run \ -v /etc/ov/machine.kek:/run/secrets/machine.kek:ro \ myapp:latestThe container never has network access to your vault credentials — just the machine key for decrypting the specific app’s secrets.
systemd service
Section titled “systemd service”[Unit]Description=My AppAfter=network.target
[Service]Type=simpleUser=deployExecStart=/usr/local/bin/ov run \ --machine-key /etc/ov/machine.kek \ --app myapp \ -- /opt/myapp/serverRestart=on-failureRestartSec=5
[Install]WantedBy=multi-user.targetsystemctl daemon-reloadsystemctl enable --now myappThe service account (deploy) must own /etc/ov/machine.kek and the file must be chmod 600.
Key rotation
Section titled “Key rotation”If you suspect the key file is compromised:
-
Revoke the OV API key for this deployment immediately — this cuts off access regardless of whether the attacker has the machine key file. A machine key decrypts ciphertext, but the ciphertext still has to be fetched from the API, so a revoked key makes the stolen file inert.
Terminal window ov apikey list # locate this deployment's key by NAME / PREFIX, copy its IDov apikey revoke <id> --confirm # alias of `ov apikey delete`; immediate and irreversibleIt takes effect server-side immediately. Any still-valid key of yours can revoke; no browser is needed during incident response. (The dashboard at app.opaquevault.com → API Keys → Revoke calls the same
DELETE /v1/apikeys/{id}route.) -
Create a replacement key with
ov apikey create --name <name> --scope read [--expires 90d](the plaintext is printed once on stdout; the CLI does not store it) and update the deployment’s~/.config/ov/config.toml(ov auth loginis interactive-only, so it is not an option on an unattended host — see CI/CD). -
Rotate the machine key:
Terminal window ov auth derive-machine-key --scope myapp/production --out /etc/ov/machine.kek --forcePreserve the deployment’s original explicit selectors, repeating
--scope app[/env]when it previously authorized more than one partition.
Security properties
Section titled “Security properties”| Property | Detail |
|---|---|
| Permission enforcement | Hard error if not exactly chmod 600 and owned by current user — no silent warnings |
| Symlink protection | Opened with O_NOFOLLOW — symlinks to other files are rejected at the OS level |
| TOCTOU safety | Permissions checked on the open file descriptor via fstat, not a separate stat call |
| Memory protection | Scoped keyring buffers are passed to mlock on Linux and macOS. Failure is non-fatal, so swapping remains possible. |
| Core dump exclusion | On Linux, scoped keyring buffers are passed to MADV_DONTDUMP; failure is non-fatal. Darwin has no per-mapping equivalent. |
| Buffer cleanup | Closing the loaded keyring or session is the cleanup boundary for its owned derived-key buffers. |
Troubleshooting
Section titled “Troubleshooting”machine key has permissions 0644 — must be 0600
chmod 600 /etc/ov/machine.kekmachine key is owned by uid 0 but current uid is 1001
The file is owned by root but the service runs as a different user. Fix ownership:
chown deploy:deploy /etc/ov/machine.kekmachine-key file predates egress policy (format v2)
Re-derive the file from the master password with the original explicit scopes, then restart the daemon and MCP bridges that use it:
ov auth derive-machine-key --scope myapp/production --out /etc/ov/machine-v3.keyopening machine key: too many levels of symbolic links
The path is a symlink. ov run refuses symlinks by design. Use the real file path.