Server install
Last reviewed:
The main install paths (npx, Homebrew, npm -g) assume you're on a desktop. This page covers the other cases: running keyrotate on a Linux server / VPS / bastion / CI runner / Docker, on Windows, and how to authenticate to your vault without biometrics.
Linux server (headless VPS / bastion / CI)
The smallest install — drop a single static binary on the box, sign in once.
# 1. Install the binary (single file, no Node/Bun runtime required) curl -fsSL https://raw.githubusercontent.com/Prompto-Studio/keyrotate/main/scripts/install.sh | bash # puts keyrotate (and `kr` alias) at ~/bin/ # 2. Install destination CLIs you need (apt example) sudo apt update && sudo apt install -y unzip # GitHub CLI type -p curl >/dev/null || sudo apt install -y curl curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list sudo apt update && sudo apt install -y gh # Bitwarden CLI (recommended vault for headless boxes — see below) npm install -g @bitwarden/cli # requires Node 18+
Headless vault auth: pick Bitwarden, not 1Password
1Password's op CLI requires biometric unlock by default — fine on a Mac, painful on a headless server. The cleanest headless story is Bitwarden, whose bw CLI supports a fully non-interactive API-key login that you can drop into systemd or a Docker entrypoint.
# 1. Generate an API key in Bitwarden web vault → Settings → My Account → API Key # 2. Export the credentials export BW_CLIENTID="user.xxxxxx" export BW_CLIENTSECRET="xxxxx" export BW_PASSWORD="your-master-password" # 3. Authenticate and unlock — no prompts bw login --apikey export BW_SESSION="$(bw unlock --passwordenv BW_PASSWORD --raw)" # 4. keyrotate can now write to Bitwarden without further input keyrotate doctor # confirms `bw` reachable + unlocked keyrotate rotate openai
Store BW_CLIENTID / BW_CLIENTSECRET / BW_PASSWORD in /etc/keyrotate.env with mode 0600, or in your CI's secret store. The BW_SESSION token expires; either re-unlock per cron run or pin a long-lived session in /run/keyrotate/session with a systemd unit that refreshes it.
Running rotations from a cron job
# /etc/cron.d/keyrotate-weekly — Sunday 03:00, rotate the Resend key
0 3 * * 0 steve /home/steve/bin/keyrotate rotate resend-alerts >> /var/log/keyrotate.log 2>&1
For interactive providers (anything where the rotate flow prompts for a value), the cron version isn't going to ask you for a key — you'd need v00.00.17's auto-rotation feature (create() capability) which generates the new key over the provider's API. That ships in the next release.
Docker
A minimal image suitable for one-shot rotations or a long-running scheduled container:
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates gnupg nodejs npm \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://raw.githubusercontent.com/Prompto-Studio/keyrotate/main/scripts/install.sh | bash
RUN npm install -g @bitwarden/cli
ENV PATH="/root/bin:${PATH}"
WORKDIR /app
COPY keyrotate.toml ./
ENTRYPOINT ["keyrotate"]
CMD ["doctor"]
Run with vault creds injected from the host's secret store:
docker run --rm \ -e BW_CLIENTID -e BW_CLIENTSECRET -e BW_PASSWORD \ -v $(pwd)/keyrotate.toml:/app/keyrotate.toml:ro \ yourorg/keyrotate \ rotate resend-alerts
Windows
keyrotate ships a Windows x64 binary. Install via npm or the curl-equivalent PowerShell flow:
# 1. Install Node 18+ from https://nodejs.org/ or via winget winget install OpenJS.NodeJS.LTS # 2. Install keyrotate npm install -g keyrotate # 3. Install destination CLIs you need winget install 1Password.CLI # `op` — interactive vault winget install GitHub.CLI # `gh` winget install Supabase.CLI winget install Netlify.Netlify winget install Fly-io.flyctl # 4. Sign each one in op signin gh auth login supabase login netlify login flyctl auth login # 5. Verify keyrotate sees them all keyrotate doctor
.env files on Windows
The envfile destination uses Node's path.resolve(), which is fully Windows-aware. .env files work identically on Windows — same format, same semantics. Point at either a relative path (.env.local) or an absolute one (C:\\Users\\you\\project\\.env).
If you'd rather set machine-wide environment variables instead of a .env file (rare in modern dev workflows but supported by Windows):
# PowerShell — set a user-scoped persistent env var
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-…", "User")
This isn't currently a destination plugin — most projects expect a .env file. If you have a strong use case for system-env destinations, open a Discussion and we'll consider it.
Cron equivalent on Windows: Task Scheduler
schtasks /create /tn "keyrotate-daily" ` /tr "C:\Users\you\AppData\Roaming\npm\keyrotate.cmd doctor --check-rotations" ` /sc daily /st 09:00
Importing existing keys (any OS)
The most common server-install scenario isn't "I'm rotating" — it's "I already have keys in .env and I want to bring them under management without making new ones." For that:
# 1. Scan everywhere keyrotate can read for existing keys keyrotate discover # 2. For each provider you found, add a rotation in keyrotate.toml # (either via `keyrotate setup` interactively, or by editing the file directly) # 3. Import the existing key — it's verified against the provider, then # written to all destinations (1Password / Bitwarden / GitHub / .env / etc). # Nothing is rotated; nothing is revoked. keyrotate import openai keyrotate import resend-alerts keyrotate import stripe
From that point on, all subsequent rotations go through keyrotate. The audit log records the import as the first event in each key's history.
Troubleshooting
bw statussays "locked" — yourBW_SESSIONexpired. Runexport BW_SESSION="$(bw unlock --passwordenv BW_PASSWORD --raw)"again, or pin a session token via a systemd timer.oprequires Touch ID on Linux — it doesn't have Touch ID. Use Bitwarden instead, or use 1Password Connect (self-hosted Docker container with service-account auth). Connect is a heavier lift but the most secure option for enterprise deployments.- Cron rotation fails silently — cron's PATH doesn't include
~/binby default. Either use the absolute path to the keyrotate binary in your crontab line, or setPATH=/usr/local/bin:/home/you/bin:/usr/bin:/binat the top of the crontab.