tx-agent-kit
Infrastructure

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:

  1. Template files: committed to git, contain op:// URI references instead of actual secret values.
  2. 1Password vaults: store the actual secret values, organized by environment.
  3. op inject: resolves op:// references at deploy time, producing a rendered .env file.

URI format

op://<vault>/<item>/<field>

For example:

DATABASE_URL=op://<project-vault>/prod/DATABASE_URL
AUTH_SECRET=op://<project-vault>/prod/AUTH_SECRET

Vault organization

Secrets are split between two vaults by scope:

VaultPurposeExample references
api-keysAccount-level, shared across every project you buildop://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):

ItemFieldUsed by
Cloudflareaccount_id, api_tokenR2 storage (account-level), Cloudflare CLI
OpenRouterapi_keyAI routing (packages/infra/ai)
Resendapi_keyTransactional email (packages/infra/email)
OpenAIapi_keyDirect OpenAI SDK calls (if any)
Anthropicapi_keyDirect Anthropic SDK calls (if any)
Geminiapi_keyDirect Gemini SDK calls (if any)
ElevenLabsapi_keyVoice generation
HuggingFacetokenHF Inference API
Apifyapi_keyData extraction workers
BrightDataapi_keyWeb scraping
DataForSEOlogin, passwordSERP data
Sentryauth_tokenOrg-level Sentry CLI (releases, source maps)
GitHubpersonal_access_tokengh CLI used in scripts
1Passwordservice_account_tokenCI/automation signing in to op

Live in <project-vault>/<env> (per-project, per-deployment):

  • AUTH_SECRET — signing identity, must be unique per deployment
  • DATABASE_URL — per-env Postgres connection
  • STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, plan price IDs — different Stripe accounts dev vs prod
  • GOOGLE_OIDC_CLIENT_ID, GOOGLE_OIDC_CLIENT_SECRET — different OAuth apps per environment
  • R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY — bucket-scoped IAM tokens
  • LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY — Langfuse scopes credentials per project
  • NEXT_PUBLIC_SENTRY_DSN, API_SENTRY_DSN, WORKER_SENTRY_DSN — per-project DSNs
  • TEMPORAL_API_KEY — per-namespace
  • RESEND_FROM_EMAIL — project-specific sender identity (the RESEND_API_KEY is 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:

VariablePurposeExample
OP_VAULT1Password vault nametx-agent-kit-services
OP_ENVEnvironment item name within the vaultstaging, 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_PROJECT

Before 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:configure

This 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.env

The 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.rendered

Security rules

RuleDetails
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 readsSource code must use typed config modules (apps/api config, apps/web/lib/env.ts, apps/worker/src/config/env.ts)
CLI guardDeploy scripts verify that op is available before attempting to inject secrets

On this page