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.