Secrets Management
1Password CLI integration for secure secrets injection across environments
tx-agent-kit uses 1Password CLI (op) for secrets management across all environments. Secrets are never stored as plaintext in the repository. Instead, committed template files contain op:// URI references that are resolved at runtime.
How it works
The secrets workflow has three components:
- Template files: committed to git, contain
op://URI references instead of actual secret values. - 1Password vaults: store the actual secret values, organized by environment.
op inject: resolvesop://references at deploy time, producing a rendered.envfile.
URI format
op://<vault>/<item>/<field>For example:
DATABASE_URL=op://<project-vault>/prod/DATABASE_URL
AUTH_SECRET=op://<project-vault>/prod/AUTH_SECRETVault organization
Secrets are split between two vaults by scope:
| Vault | Purpose | Example references |
|---|---|---|
api-keys | Account-level, shared across every project you build | op://api-keys/OpenRouter/api_key, op://api-keys/Cloudflare/account_id |
<project-vault> | Per-project, per-environment (dev, staging, prod) | op://<project-vault>/prod/DATABASE_URL, op://<project-vault>/dev/STRIPE_SECRET_KEY |
Which vault does a secret live in?
Use this rule: if the exact same value would be used by every project you build, it belongs in api-keys.
Live in api-keys (centralized, one item per service):
| Item | Field | Used by |
|---|---|---|
Cloudflare | account_id, api_token | R2 storage (account-level), Cloudflare CLI |
OpenRouter | api_key | AI routing (packages/infra/ai) |
Resend | api_key | Transactional email (packages/infra/email) |
OpenAI | api_key | Direct OpenAI SDK calls (if any) |
Anthropic | api_key | Direct Anthropic SDK calls (if any) |
Gemini | api_key | Direct Gemini SDK calls (if any) |
ElevenLabs | api_key | Voice generation |
HuggingFace | token | HF Inference API |
Apify | api_key | Data extraction workers |
BrightData | api_key | Web scraping |
DataForSEO | login, password | SERP data |
Sentry | auth_token | Org-level Sentry CLI (releases, source maps) |
GitHub | personal_access_token | gh CLI used in scripts |
1Password | service_account_token | CI/automation signing in to op |
Live in <project-vault>/<env> (per-project, per-deployment):
AUTH_SECRET— signing identity, must be unique per deploymentDATABASE_URL— per-env Postgres connectionSTRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, plan price IDs — different Stripe accounts dev vs prodGOOGLE_OIDC_CLIENT_ID,GOOGLE_OIDC_CLIENT_SECRET— different OAuth apps per environmentR2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY— bucket-scoped IAM tokensLANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY— Langfuse scopes credentials per projectNEXT_PUBLIC_SENTRY_DSN,API_SENTRY_DSN,WORKER_SENTRY_DSN— per-project DSNsTEMPORAL_API_KEY— per-namespaceRESEND_FROM_EMAIL— project-specific sender identity (theRESEND_API_KEYis centralized, but the FROM address is per-project)
Rotation blast radius
Centralization reduces rotation friction (one place to update a leaked key) but widens blast radius if that key leaks. Only centralize values that are already effectively shared because they come from a single upstream account — never centralize per-deployment secrets like AUTH_SECRET or STRIPE_WEBHOOK_SECRET.
Template files
Two deploy templates are committed to the repository: deploy/env/staging.env.template for the staging environment and deploy/env/prod.env.template for production.
Each template declares two configuration variables at the top that control which 1Password vault and environment item to resolve secrets from:
| Variable | Purpose | Example |
|---|---|---|
OP_VAULT | 1Password vault name | tx-agent-kit-services |
OP_ENV | Environment item name within the vault | staging, prod |
All op:// references in the template use ${OP_VAULT} and ${OP_ENV} instead of hardcoded vault or environment names. This means the same template structure works for any vault and environment combination -- you only need to set the two variables at the top.
These templates mix literal values (like NODE_ENV=staging) with variable-based op:// references for sensitive values:
# 1Password Vault Configuration
# Set these to your 1Password vault and environment names
OP_VAULT=<vault>
OP_ENV=<environment>
# Literal values are committed as-is
NODE_ENV=staging
API_PORT=4000
API_HOST=0.0.0.0
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_SERVICE_NAMESPACE=<service_namespace>
# Secrets reference 1Password vaults via configurable variables
DATABASE_URL=op://${OP_VAULT}/${OP_ENV}/DATABASE_URL
AUTH_SECRET=op://${OP_VAULT}/${OP_ENV}/AUTH_SECRET
TEMPORAL_ADDRESS=op://${OP_VAULT}/${OP_ENV}/TEMPORAL_ADDRESS
GOOGLE_CLOUD_PROJECT=op://${OP_VAULT}/${OP_ENV}/GOOGLE_CLOUD_PROJECTBefore running op inject, replace <vault> and <environment> with actual values (e.g., OP_VAULT=tx-agent-kit-services and OP_ENV=staging). The OTEL_SERVICE_NAMESPACE placeholder should also be set to your project's service namespace.
Local development
For local development, secrets are not required from 1Password. The pnpm env:configure script generates a .env file with safe local defaults:
pnpm env:configureThis writes defaults like AUTH_SECRET=<generated-local-secret> and DATABASE_URL=postgres://postgres:postgres@localhost:5432/<domain_identifier>.
The .env file is gitignored and never committed.
Deployment workflow
During deployment, op inject renders the template into a usable .env file:
# Render staging secrets
op inject -i deploy/env/staging.env.template -o /tmp/staging.env
# Render production secrets
op inject -i deploy/env/prod.env.template -o /tmp/prod.envThe deploy scripts (pnpm deploy:staging, pnpm deploy:prod) handle this automatically. The rendered file is passed to Docker Compose via DEPLOY_ENV_FILE.
CLI operations
# Read a single secret
op read "op://<project-vault>/staging/DATABASE_URL"
# Run a command with injected secrets
op run --env-file=deploy/env/staging.env.template -- <command>
# Inject template into file
op inject -i deploy/env/staging.env.template -o .env.renderedSecurity rules
| Rule | Details |
|---|---|
| No committed secrets | .env files are gitignored. Never commit rendered secrets |
| Templates are safe | .env.template, .env.dev, .env.prod contain only op:// references |
| No direct env reads | Source code must use typed config modules (apps/api config, apps/web/lib/env.ts, apps/worker/src/config/env.ts) |
| CLI guard | Deploy scripts verify that op is available before attempting to inject secrets |