Two scripts on my agent box both needed the password for my uptime monitor. Each kept its own copy. When I finally tested them side by side, one logged in and saw every monitor, and the other had been failing authentication for who knows how long:
script A LOGIN OK
script B FAILED: authIncorrectCreds
Nothing had alerted, because nothing compared the two. Two copies of a secret turn out to be one copy and one liability, and nothing tells you which is which.
The same afternoon I found a third script that rotated its own machine credential by rewriting its own source file. That file sat in a directory a nightly backup pushed to my git server. So the rotation worked beautifully, and every rotated credential ended up in version control.
Both are fixed now. This post is the decision behind it: why the machine secrets live in a self-hosted Infisical rather than 1Password, which I'd otherwise happily recommend. My reasons fit on one line: it's hosted locally, I know where the cryptographic keys live, and I can manage all of it from the CLI.
Reason one: it runs on my LAN
Infisical runs as the official native package, the omnibus build supervised by systemd, inside its own small Proxmox container. Not Docker, not the cloud.
Infisical's own docs pitch self-hosting as keeping "your data on your own infrastructure and network", and for a homelab that's the whole appeal. The things fetching secrets are cron jobs, backup scripts, a monitoring daemon and a handful of agents, all on the same network. A secret request doesn't need to leave the house, and when my internet connection has a bad evening (it does), secret fetches don't care.
Local has a limit, though. My monitoring daemon deliberately does not depend on Infisical being reachable at the moment it runs, because a daemon whose job is noticing the network is down shouldn't need a network round-trip to find out. Self-hosting shortens the dependency chain but it doesn't remove it.
Reason two: I know where the keys live
This is where I want to be fair, because 1Password's cryptography is excellent and I'm not claiming otherwise.
1Password's security design white paper describes true end-to-end encryption: keys are generated on your devices, all encryption happens locally, and a "two-secret key derivation" mixes your account password with a locally held Secret Key so that what they store can't be used to crack your password. Vault items use AES-256-GCM. The company is never in a position to learn your keys. That's a strong design.
Infisical's model is different. Its security page says secrets are encrypted at rest with AES-256-GCM under a layered key hierarchy: an operator-supplied root encryption key (passed in as an environment variable) encrypts an internal KMS root key, which in turn encrypts per-organisation and per-project data keys. Their stated goal is that "compromising the database alone is insufficient to decrypt sensitive data". The self-hosting configuration docs make ENCRYPTION_KEY a required setting.
So the question for me was never whose maths is stronger. It was where the ciphertext lives and who holds the key at the top. With 1Password the ciphertext sits on their servers and the unlocking secrets sit on my devices. With self-hosted Infisical, the database, the server and the root key all sit on hardware I can touch. For machine secrets, read by unattended processes at three in the morning, I prefer the arrangement where I run the whole chain.
The honest price is that I'm now the one guarding the root key. If I lose it, the database is noise. If I leak it alongside a database backup, the layering stops helping. I also own the patching, the backups and the restore test. Nobody pages Infisical's on-call when my container falls over. That's a real cost, and anyone who tells you self-hosting a secrets manager is free is selling something too.
Reason three: the CLI is the interface
My scripts don't have a human to type a master password, so they log in as a machine identity using universal auth: a client ID and client secret swapped for a short-lived access token. The Infisical CLI does that in one line, and the shape in my shell scripts is roughly this:
# point the CLI at the self-hosted instance, not Infisical Cloud
export INFISICAL_TOKEN=$(infisical login --method=universal-auth \
--client-id="$CLIENT_ID" --client-secret="$CLIENT_SECRET" \
--domain="$INFISICAL_URL" --plain --silent)
infisical secrets get SOME_NAME --domain="$INFISICAL_URL" \
--projectId="$PROJECT_ID" --env=prod --path=/SomeFolder --plain --silent
--plain prints just the value and --silent hides the update tips, which matters when stdout is being captured into a variable. If you self-host, note the docs' warning that the domain has to be set on every command (or via an environment variable), otherwise the CLI quietly heads for the US cloud. The CLI on my box is v0.38.0.
For services that take environment variables, infisical run -- <command> injects secrets into the process, and infisical export can write dotenv, JSON or YAML if something insists on a file.
Three habits grew out of driving it this way.
Names, never values, in anything automated. A nightly inventory script uses the CLI to list secret names per folder, so my estate notes can say "this folder holds these four things" without ever touching a value. When I need to know two copies agree, I compare hashes.
Delete explicitly. On my version, deleting a secret as a machine identity failed with "Must be user to delete personal secret". My first thought was that the error was wrong, but it wasn't: the CLI had created that secret as a personal secret, and deleting it needed --type=shared. The surprise was the default, not the message.
Python goes through one resolver. The Python services never call the CLI. They import one small module that resolves environment, then Infisical, then an explicit default, and raises rather than returning something wrong. Login is cached, so it's one round-trip per process, and resolution happens at the call rather than at import, so an Infisical blip surfaces as a clear error inside the function that needed the secret instead of killing an import. That module is what fixed the two disagreeing scripts: both now ask the same question of the same place.
The machine identity's own client ID and secret still have to live somewhere, because a bootstrap credential can't fetch itself. Mine sits in a locked-down file on the box, readable only by the account that needs it. The self-rewriting script now updates that file atomically and clears the token cache, instead of editing its own source.
Humans and machines are different problems
I don't use Infisical for my own passwords. Those live in Vaultwarden, the self-hosted Bitwarden-compatible server, with around 753 items. The split is simple: humans go to Vaultwarden, machines go to Infisical.
I found out why it matters by getting it wrong. At one point the Vaultwarden master password was stored in Infisical so automation could unlock the vault. That quietly collapsed the security of my entire password vault onto the agent box and the credential it logs in with. I deleted those entries. The consequence is deliberate: nothing on the estate can unlock my personal vault without me.
The same boundary shows up in small rules. When a bot needed a website session cookie, the rule was that the cookie value goes into Infisical and the script pulls it at runtime. It never gets pasted into a chat window, however convenient that would be.
The same thinking applies between machines. Most consumers only ever read a secret; the handful of jobs that rotate one are the only things that should be able to write. For a long time that wasn't true here: every script read with an identity that could also write and delete. I know because I didn't take its permissions on trust: I probed it with a throwaway secret, and it could write and delete. (That probe is also where the personal-secret surprise above came from.) The fix was a second machine identity with the project's built-in Viewer role, which my resolver now uses for every read. The install script only keeps it if a test read works and a test write is refused, and the API's answer to that write was a flat "not allowed to create on secrets". The two jobs that genuinely write, sync and rotation, keep the other identity.
What 1Password does better
Plenty, and it's worth saying plainly.
- End-to-end encryption with server ignorance. 1Password can't read your data. My Infisical server can decrypt everything, because it has to hand secrets to machines.
- No root key to babysit, no server to patch. Their operations team is better at uptime than my Proxmox box.
-
A good CLI of its own.
op read,op runandop injectcover the same patterns as mine, usingop://vault/item/fieldsecret references. - Machine access without running anything. Service accounts automate secrets "without the need to deploy additional services". If you do want something local, a Connect server runs in your infrastructure and caches your data there, though it keeps that data in sync with 1Password.com and needs a 1Password account behind it. It's a local cache of a cloud vault, not a self-hosted vault.
- Humans. Sharing, recovery, apps that non-technical family members will actually use. Nothing I've built competes with that.
What I'd tell someone deciding
- If you'd rather not own a root key, backups and upgrades, use 1Password. That's a perfectly sensible answer, not a lesser one.
- If your consumers are all on your LAN and you already run backups you've actually restored, self-hosting machine secrets is very reasonable. Budget for the patching.
- Whichever you pick, keep humans and machines apart. The key to your personal vault should never be readable by a script.
- Drive it from the CLI and a single resolver, not from copies. The tool matters less than the fact that there's only one place to ask.
- Give readers and writers different identities, and test what each one can really do.
I didn't pick Infisical because 1Password is weak. I picked it because I wanted to know where every key lives, including the one at the top, and the only way to know that for certain was to hold it myself.
🤖 Drafted with AI assistance from my own homelab notes, logs and repos, then reviewed and edited before publishing.












