Skip to content

CI/CD

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

OpaqueVault works in fully automated environments via machine keys — a 32-byte key file you derive once interactively, then use non-interactively in any pipeline.


  1. Derive a machine key once on your workstation (one interactive password prompt)
  2. Place the key file on the target system with chmod 600
  3. Use ov run --machine-key <path> — no password prompt, no terminal required

That’s it. One key file per deployment, scoped to a single app, protected by filesystem permissions.


  1. Create a scoped API key for the deployment

    Use a dedicated read-only API key for each deployment rather than your personal key:

    Terminal window
    ov apikey create --name ci --scope read
    # → ov_live_ci_a3f9b2c1... (store this, shown once)

    Configure ov to use it on the target system:

    Terminal window
    # On the CI runner or server
    ov auth login --api-key ov_live_ci_a3f9b2c1...
  2. Derive the machine key (once, on your workstation)

    Terminal window
    ov auth derive-machine-key --out ./machine.kek --app my-saas
    # prompts for your master password, then writes ./machine.kek

    The --app flag scopes the key — a compromised key for my-saas cannot decrypt secrets from other apps.

  3. Transfer the key to the target system securely

    Terminal window
    # Example: scp to a server
    scp -p ./machine.kek deploy@server:/etc/ov/machine.kek
    ssh deploy@server "chmod 600 /etc/ov/machine.kek && ls -la /etc/ov/machine.kek"
    # -rw------- 1 deploy deploy 32 ...

    Delete the local copy after transfer.

  4. Run without a prompt

    Terminal window
    ov run --machine-key /etc/ov/machine.kek --app my-saas -- ./bin/server

Store the machine key as a base64-encoded GitHub secret (OV_MACHINE_KEY):

Terminal window
# On your workstation — encode for GitHub secrets
base64 -i machine.kek | pbcopy # macOS
base64 -w0 machine.kek | xclip # Linux

Add to your repo: Settings → Secrets → New repository secretOV_MACHINE_KEY.

Then in your workflow:

name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ov
run: curl -fsSL https://get.opaquevault.com | sh
- name: Configure ov
run: ov auth login --api-key ${{ secrets.OV_API_KEY }}
- name: Write machine key
run: |
mkdir -p /tmp/ov
echo "${{ secrets.OV_MACHINE_KEY }}" | base64 -d > /tmp/ov/machine.kek
chmod 600 /tmp/ov/machine.kek
- name: Run tests
run: ov run --machine-key /tmp/ov/machine.kek --app my-saas -- go test ./...
- name: Deploy
run: ov run --machine-key /tmp/ov/machine.kek --app my-saas -- ./scripts/deploy.sh

Two secrets in GitHub (OV_API_KEY + OV_MACHINE_KEY). All your real secrets stay behind zero-knowledge encryption — the machine key decrypts them locally in the runner.


Bind-mount the key file read-only at container startup:

# Dockerfile
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 machine key is never baked into the image — it’s injected at runtime from the host.


/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 deploy user must own /etc/ov/machine.kek with chmod 600.


If a machine key is compromised:

  1. Revoke the deployment API key immediately — this cuts off vault access regardless of the key file:
    Terminal window
    ov apikey list
    ov apikey delete <key-id>
  2. Create a new API key for the deployment.
  3. Rotate the machine key:
    Terminal window
    ov auth derive-machine-key --out machine.kek --app my-saas --force
    # transfer new key to server, update any secrets that stored it