Skip to content

Machine Keys (Non-interactive)

import { Aside, Steps } from ‘@astrojs/starlight/components’;

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 32-byte key file once (interactively, as a human), then automated processes read it to load the KEK without prompting.


ov auth derive-machine-key → writes raw 32-byte KEK to a chmod 600 file
ov run --machine-key <path> → reads file, loads KEK, runs without prompting

The key file is raw binary — 32 bytes, no JSON wrapper, no headers. 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.


  1. Log in and create your app

    If you haven’t already:

    Terminal window
    ov auth login
    ov app create myapp
    ov secret set DATABASE_URL --app myapp
  2. 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 --out /etc/ov/machine.kek --app myapp

    The --app flag scopes the key to a single app. A scoped key cannot decrypt secrets from other apps, limiting blast radius if the file is ever compromised.

    You’ll see:

    Master password: ▌
    ⚠ Machine key written to /etc/ov/machine.kek
    This file is as sensitive as your master password.
    It grants decryption access to secrets.
    - 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 --out /etc/ov/machine.kek --force

    Verify the permissions:

    Terminal window
    ls -la /etc/ov/machine.kek
    # -rw------- 1 deploy deploy 32 Apr 15 10:00 /etc/ov/machine.kek
  3. 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.


#!/usr/bin/env bash
set -euo pipefail
ov run \
--machine-key /etc/ov/machine.kek \
--app myapp \
--secrets DATABASE_URL,REDIS_URL \
-- ./bin/server

The process starts with DATABASE_URL and REDIS_URL in its environment. They are never written to disk, never logged, and never visible in ps output.


Store the machine key on the host and bind-mount it read-only:

# Dockerfile — no secrets baked in
FROM debian:bookworm-slim
RUN curl -fsSL https://get.opaquevault.com | sh
COPY ./bin/server /app/server
ENTRYPOINT ["ov", "run", "--machine-key", "/run/secrets/machine.kek", "--app", "myapp", "--", "/app/server"]
Terminal window
docker run \
-v /etc/ov/machine.kek:/run/secrets/machine.kek:ro \
myapp:latest

The container never has network access to your vault credentials — just the machine key for decrypting the specific app’s secrets.


/etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
User=deploy
ExecStart=/usr/local/bin/ov run \
--machine-key /etc/ov/machine.kek \
--app myapp \
-- /opt/myapp/server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Terminal window
systemctl daemon-reload
systemctl enable --now myapp

The service account (deploy) must own /etc/ov/machine.kek and the file must be chmod 600.


If you suspect the key file is compromised:

  1. Revoke the OV API key for this deployment immediately — this cuts off access regardless of whether the attacker has the machine key file:

    Terminal window
    ov apikey list
    ov apikey delete <key-id>
  2. Create a new API key and update the deployment config.

  3. Rotate the machine key:

    Terminal window
    ov auth derive-machine-key --out /etc/ov/machine.kek --force

PropertyDetail
Permission enforcementHard error if not exactly chmod 600 and owned by current user — no silent warnings
Symlink protectionOpened with O_NOFOLLOW — symlinks to other files are rejected at the OS level
TOCTOU safetyPermissions checked on the open file descriptor via fstat, not a separate stat call
Memory protectionKEK bytes are mlocked on load (Linux/macOS) — cannot be swapped to disk
Core dump exclusionKEK memory marked MADV_DONTDUMP on Linux — excluded from crash reports
App scoping--app flag derives a per-app key via HKDF — one leaked file ≠ all apps
Zero after useKEK is zeroed from memory after the subprocess exits

machine key has permissions 0644 — must be 0600

Terminal window
chmod 600 /etc/ov/machine.kek

machine 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:

Terminal window
chown deploy:deploy /etc/ov/machine.kek

machine key must be exactly 32 bytes

The file is corrupted or was created incorrectly. Re-derive it:

Terminal window
ov auth derive-machine-key --out /etc/ov/machine.kek --force

opening machine key: too many levels of symbolic links

The path is a symlink. ov run refuses symlinks by design. Use the real file path.