Ringtail
Concepts

Project-scoping

Ringtail reads one project's .env.example as the manifest โ€” and fans each key out across the environment axis.

Ringtail is project-scoped. You point it at a local project and it reads that project's .env.example as the manifest โ€” the shopping list of every credential the project needs. The connection grid is built directly from it: providers ร— environments.

Only names and paths cross the boundary during selection โ€” never file contents, nothing secret.

The .env.example format

Ringtail groups the keys in your .env.example into one provider row per section. You do not need any special format โ€” the normal way you already write .env.example works. There are three ways a key gets grouped, in order:

1. A plain section comment (what most projects already use):

# Database
DATABASE_URL=

## Auth
BETTER_AUTH_SECRET=

# ---- Email ----
RESEND_API_KEY=

Each # Header, ## Header, or # ---- Header ---- comment starts a new row. Decorative dashes/equals/asterisks around the label are ignored, and a prose comment (one containing a URL, colon, or other sentence punctuation โ€” e.g. # get this from the dashboard โ†’) is left as a plain comment, not treated as a header.

2. The box-drawing section comment (Ringtail's own repos use this โ€” still supported):

# โ”€โ”€ Cloudflare โ”€โ”€
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_ACCOUNT_ID=

3. No header at all โ€” Ringtail recognizes the provider from the variable name so a bare .env.example still splits into real provider rows:

DATABASE_URL=          # โ†’ neon
BETTER_AUTH_SECRET=    # โ†’ better-auth
RESEND_API_KEY=        # โ†’ resend
CLOUDFLARE_API_TOKEN=  # โ†’ cloudflare
STRIPE_SECRET_KEY=     # โ†’ creem (billing)

Known prefixes: DATABASE_URL / POSTGRES* / PG* / NEON* โ†’ neon, RESEND* โ†’ resend, BETTER_AUTH* โ†’ better-auth, POSTHOG* / NEXT_PUBLIC_POSTHOG* โ†’ posthog, CLOUDFLARE* / CF_* โ†’ cloudflare, CREEM* / STRIPE* / DODO* โ†’ creem, INFISICAL* โ†’ infisical. Anything unrecognized and without a header lands in an other row โ€” still on the grid, just not auto-mapped to a recipe.

As always, only names are read โ€” the right-hand side of .env.example holds no values, so nothing secret is ever touched.

The environment axis

local ยท dev ยท staging ยท prod.

  • local โ€” your machine, .env.local, localhost. The only environment that touches your disk.
  • dev ยท staging ยท prod โ€” deployed. Secrets go to Infisical only, never your disk. Each environment gets its own scoped keys and resources (e.g. a Neon branch per env).

Sink routing follows the axis: local โ†’ .env.local; dev / staging / prod โ†’ Infisical. syncCredential fans one acquired key out per environment so a single consent provisions the whole row.

Local credential discovery

Before asking you for anything, Ringtail scans known credential stores โ€” .env.local, ~/.ringtail, ~/.aws/credentials, ~/.config/gh, ~/.wrangler / ~/.cloudflared, and the environment โ€” validates what it finds, reuses complete root grants, and only prompts for real gaps. Known locations only, local-only, and transparent about what it reused. It never scans your whole disk.