Stop Committing Secrets. Use 1Password CLI.
Series: You Don’t Need a VPC | Post 4 of 5
The Problem With .env Files
Every home server tutorial ends with “create a .env file and add it to .gitignore.” This works until:
- You set up a second machine and forget to copy the file
- You rotate a credential and update it in two places inconsistently
- Someone (you) accidentally commits it
- You want to share the repo publicly
The better pattern: secrets live in a password manager, fetched at deploy time. No file to lose, no file to commit, no drift.
The Pattern
deploy.sh fetches all secrets from 1Password before calling podman-compose:
#!/bin/bash
set -euo pipefail
# Fetch secrets from 1Password
_op() { op item get "Home Server" --vault Chill --field "$1" --reveal; }
export CLOUDFLARE_TUNNEL_TOKEN=$(_op cloudflare_tunnel_token)
export OAUTH2_COOKIE_SECRET=$(_op oauth2_cookie_secret)
export OBSIDIAN_LIVESYNC_PASSWORD=$(_op obsidian_livesync_password)
# ... more secrets
# Write to tmpfs for processes that need env files
SECRETS_FILE="/run/user/$(id -u)/services-secrets.env"
env | grep -E "^(CLOUDFLARE|OAUTH2|OBSIDIAN)_" > "$SECRETS_FILE"
chmod 600 "$SECRETS_FILE"
exec podman-compose "$@"Secrets exist in memory during the deploy and in a tmpfs file (cleared on reboot) — never on disk in plaintext.
Adding a New Secret
# Add to 1Password
op item edit "Home Server" --vault Chill "my_new_secret[concealed]=the_value"
# Add to deploy.sh
export MY_NEW_SECRET=$(_op my_new_secret)
# Reference in docker-compose.yml
environment:
- MY_SECRET=${MY_NEW_SECRET}That’s it. The secret is now version-controlled as a reference, not a value.
Multiple Credentials for the Same Service
Some services need multiple accounts. PushPress (a gym management platform) has separate logins per gym. Each credential is a separate 1Password item, referenced by UUID:
export PUSHPRESS_PASSWORD_GYM1=$(op item get "op://Chill/abc123def456/password" --reveal)
export PUSHPRESS_PASSWORD_GYM2=$(op item get "op://Chill/xyz789uvw012/password" --reveal)Using UUIDs instead of item names means renames don’t break anything.
Automation: Service Account Token
For the systemd boot unit (runs without a logged-in user), op needs non-interactive auth. Use a 1Password service account:
export OP_SERVICE_ACCOUNT_TOKEN="ops_..."Set this in the systemd unit’s environment. The service account gets read-only access to the specific vault — minimal permissions, no MFA required.
What This Looks Like in the Repo
The public repo has no secrets, no .env.example with real structure hints, no hardcoded values. docker-compose.yml uses ${VAR_NAME} everywhere. Anyone cloning the repo sees the shape of the config but none of the values.
This is the model cloud teams use for CI/CD with Vault or AWS Secrets Manager — just 1Password instead.
Next Up
Post 5: OAuth2 in front of every service, managed as code.