DigitalOcean & SSH servers
This guide puts tvault on a Linux server (a DigitalOcean droplet, but anything with SSH works) so you can manage that server's secrets over SSH and feed them to the apps running on it. Two models, pick per use case:
- Model A — sealed secrets, managed centrally (recommended for app config): the canonical vault stays on your laptop. You seal secrets to a per-server identity and the server decrypts them with its private key — no vault passphrase ever touches the server.
- Model B — a full vault on the server:
tvault initon the droplet and manage secrets directly over SSH withtvault set/get/list. Use when the server itself is where you want to author secrets.
Install on the droplet
tvault is a single static binary, so installing on a droplet is quick. Pick whichever fits your base image:
# Debian/Ubuntu — the .deb from the latest release
ARCH=$(dpkg --print-architecture) # amd64 or arm64
curl -fsSLO "https://github.com/abdul-hamid-achik/tinyvault/releases/latest/download/tvault_$(curl -fsSL https://api.github.com/repos/abdul-hamid-achik/tinyvault/releases/latest | grep -oE '"tag_name": *"v[^"]+"' | grep -oE '[0-9.]+')_linux_${ARCH}.deb"
sudo dpkg -i tvault_*_linux_${ARCH}.deb
# Fedora/RHEL: the .rpm · Alpine: the .apk (same release page)
# Homebrew (also works on Linux via Linuxbrew)
brew install --cask abdul-hamid-achik/tap/tvault
# Have a Go toolchain on the box?
go install github.com/abdul-hamid-achik/tinyvault/cmd/tvault@latestDon't want to hand-build a URL? Grab the tvault_<ver>_linux_<arch>.tar.gz (or .deb/.rpm/.apk) for your droplet straight from the Releases page — each release ships linux/amd64 and linux/arm64.
Staying current
Once tvault is on the box, it can update itself — checksum-verified, straight from the official releases:
tvault self-update --check # is a newer release available?
tvault self-update # download + verify + replace in placeIf you installed via Homebrew or a system package, update through that package manager instead so its bookkeeping stays correct.
Model A: sealed secrets, passphrase-free
The server holds only a keypair, never the passphrase. Compromising the droplet exposes what you sealed to it. You can remove the identity from future live-vault access centrally, but you cannot retract data the server already received.
1. On your laptop — make a per-server identity and seal to it. Generate the identity locally so the public half can be committed and the private half handed to the server:
tvault identity new web-prod # prints tvault1… (public, shareable)
tvault identity export web-prod # prints tvault-key1… (private — copy to the server)Seal the project's secrets to that recipient. The output is a commit-safe v2 blob — you can check it into the repo or scp it:
tvault seal --recipient tvault1<web-prod-public> --out .env.encrypted2. On the server — drop the private key and decrypt. Put the private key in a root-only file and decrypt with tvault open (no passphrase, no vault):
sudo install -d -m 700 /etc/tvault
printf 'TVAULT_IDENTITY_KEY=%s\n' 'tvault-key1<web-prod-private>' | sudo tee /etc/tvault/identity >/dev/null
sudo chmod 600 /etc/tvault/identity
# materialize the .env (env var picked up automatically by tvault open):
set -a; . /etc/tvault/identity; set +a
tvault open --in /etc/app/.env.encrypted --out /run/app.env3. Wire it into systemd. Decrypt to a tmpfs path just before the app starts, so plaintext never lands on disk:
# /etc/systemd/system/myapp.service
[Service]
EnvironmentFile=/etc/tvault/identity
RuntimeDirectory=myapp # /run/myapp, tmpfs, 0700
ExecStartPre=/usr/local/bin/tvault open --in /etc/app/.env.encrypted --out /run/myapp/.env
ExecStart=/usr/local/bin/myapp # reads /run/myapp/.envRotate / remove access centrally from your laptop. Editing a secret means re-seal + redeploy; removing a server from the updated live vault rotates the project DEK and re-encrypts every current value and archived version:
tvault set NUXT_DATABASE_URL "…"; tvault seal --recipient tvault1<web-prod> --out .env.encrypted # update
tvault projects unshare tvault1<web-prod> # remove from live vaultThe droplet can still read a pre-removal vault snapshot or sealed artifact it already received. Rotate the underlying credentials, re-seal without the removed recipient, and redeploy when the server or its identity may be compromised.
TIP
tvault ci init --provider github-actions --mode identity --identity web-prod scaffolds the same passphrase-free flow for a pipeline that builds and ships to the droplet.
Model B: a full vault you manage over SSH
When you want to author secrets on the server, give it a real vault.
ssh you@droplet
tvault init # creates ~/.tvault (0700), prompts for a passphrase
tvault set DATABASE_URL "postgres://…"
tvault set API_KEY "$(openssl rand -hex 24)"
tvault listFor non-interactive use (scripts, systemd, ssh droplet 'tvault …'), supply the passphrase via a root-only env file instead of the prompt:
sudo install -d -m 700 /etc/tvault
printf 'TVAULT_PASSPHRASE=%s\n' 'your-passphrase' | sudo tee /etc/tvault/env >/dev/null
sudo chmod 600 /etc/tvault/env
set -a; . /etc/tvault/env; set +a
tvault run -- myapp # injects all project secrets as env varsRun the local agent so repeated reads in one SSH session skip the prompt and the Argon2id derivation:
tvault agent start & # unix socket, 0600, peer-uid checked
tvault hook >> ~/.bashrc # optional: auto-route get/env/run through itsystemd for an app that reads from the server's vault:
# /etc/systemd/system/myapp.service
[Service]
EnvironmentFile=/etc/tvault/env # TVAULT_PASSPHRASE (0600, root-only)
ExecStart=/usr/local/bin/tvault run --only DATABASE_URL,API_KEY -- /usr/local/bin/myappWARNING
Model B keeps the passphrase on the server. Prefer Model A for app config; reserve Model B for a server you treat as a secrets-authoring host. Either way, keep /etc/tvault/* at 0600 and ~/.tvault at 0700, and never commit vault.db or a tvault-key1… private key.
Provisioning the droplet with Pulumi
The droplet, its SSH key, and the firewall are themselves IaC. Provision them with Pulumi, injecting the DigitalOcean token from the same vault (see Pulumi & IaC):
tvault run --only DIGITALOCEAN_TOKEN -- pulumi upHave the droplet install tvault and drop its identity key on first boot via cloud-init user_data:
#cloud-config
packages: [curl]
runcmd:
# install the .deb for this droplet's arch from the latest release
- ARCH=$(dpkg --print-architecture)
- VER=$(curl -fsSL https://api.github.com/repos/abdul-hamid-achik/tinyvault/releases/latest | grep -oE '"tag_name": *"v[^"]+"' | grep -oE '[0-9.]+')
- curl -fsSLO "https://github.com/abdul-hamid-achik/tinyvault/releases/latest/download/tvault_${VER}_linux_${ARCH}.deb"
- dpkg -i "tvault_${VER}_linux_${ARCH}.deb"
write_files:
- path: /etc/tvault/identity
permissions: '0600'
content: "TVAULT_IDENTITY_KEY=tvault-key1<web-prod-private>"In Pulumi, supply that user_data as a pulumi.secret() so the key stays encrypted in state.
Security checklist
~/.tvaultis0700;/etc/tvault/*env/identity files are0600and root-owned.- The server holds a private identity key (Model A) or a passphrase (Model B) — never both, and never the vault passphrase in Model A.
- Decrypt to tmpfs (
/run/...,RuntimeDirectory=), not to a persistent disk path. - Never commit
vault.dbortvault-key1….tvault1…(public) and.env.encrypted(sealed) are safe to commit. - Remove a lost server with
tvault projects unshareto re-key the updated live vault, then rotate the underlying credentials and re-seal artifacts; the old identity can still open data it retained before removal.
See also
- Pulumi & IaC — provision the droplet and deploy with secrets injected
- Committable Secrets — the
.env.encryptedv2 format andseal/open - Sharing Secrets — identities, recipients, live-vault re-keying, and retained-data limits
- Local Agent — prompt-free reads on the server
- CI/CD — passphrase-free pipelines with
TVAULT_IDENTITY_KEY