1Password Environments

what environments are

1Password Environments let you store environment variables in 1Password and access them via the CLI:

op environment read <environment-id>

This outputs KEY=VALUE pairs. Each environment has a unique ID, and your team can manage variables through the 1Password desktop app without passing .env files around.

op environment read dumps the whole environment in one call. Use a source to run it once and look up individual fields — no matter how many keys you reference, the command only runs once:

[vars]
stage = "dev"

[op.environments]
dev  = "env_abc123"
prod = "env_ghi789"

[sources.openv]
load   = "op environment read {op.environments.{vars.stage}}"
secret = true

[db]
password = "openv://DB_PASSWORD"
host     = "openv://DB_HOST"

[app]
api_key  = "openv://API_KEY"
cdn      = "openv://CDN_DOMAIN"
# dev (default) — one op environment read call for all four keys
$ confit show db

# production
$ confit --set stage=prod show db

The secret = true on the source masks every field from that environment as *** by default. Use --reveal to see real values, or confit run to inject them into a process.

per-stage environments

Map stage names to environment IDs in a [vars]-style table, then interpolate into the source load:

[vars]
stage = "dev"

[op.environments]
dev     = "env_abc123"
staging = "env_def456"
prod    = "env_ghi789"

[sources.openv]
load   = "op environment read {op.environments.{vars.stage}}"
secret = true

[db]
password = "openv://DB_PASSWORD"
host     = "openv://DB_HOST"
url      = "postgres://app:{db.password}@{db.host}/mydb"
$ confit --set stage=prod run db -- node server.js
# DB_PASSWORD, DB_HOST, DB_URL injected; op reads prod environment once

mixing with other providers

Sources compose naturally alongside providers for non-environment values:

[vars]
stage = "dev"

[op.environments]
dev  = "env_abc123"
prod = "env_ghi789"

[sources.openv]
load   = "op environment read {op.environments.{vars.stage}}"
secret = true

[providers.tf]
cmd = "terraform -chdir=iac/stages/{stage} output -raw {path}"

[db]
password = "openv://DB_PASSWORD"
host     = "tf://db_endpoint"
url      = "postgres://app:{db.password}@{db.host}/mydb"

[app]
api_key  = "openv://API_KEY"
cdn      = "tf://cdn_domain"
base_url = "https://{app.cdn}/v1"
$ confit --set stage=prod run app --upper -- node server.js
# DB_PASSWORD, DB_HOST, DB_URL, API_KEY, CDN, BASE_URL all injected
# op environment read runs once; terraform output runs per-key as needed

provider approach (alternative)

If you need per-key dispatch or want to avoid the source abstraction, the provider approach still works. It runs op environment read once per referenced variable:

[providers.openv]
cmd = "op environment read $(echo {path} | cut -d/ -f1) | sed -n \"s/^$(echo {path} | cut -d/ -f2-)=//p\""

The URI format is openv://ENVIRONMENT_ID/VAR_NAME:

[db]
password = "secret://openv://{op.environments.{vars.stage}}/DB_PASSWORD"
host     = "secret://openv://{op.environments.{vars.stage}}/DB_HOST"

This runs op environment read twice (once per key). For a section with many variables, the source approach is significantly faster.

shell eval alternative

For one-offs, shell evaluation works without declaring anything:

[db]
password = "secret://$(op environment read {op.environments.{vars.stage}} | sed -n 's/^DB_PASSWORD=//p')"

This works because confit resolves interpolation first, then evaluates $(...) commands. Like the provider approach, it runs a separate subprocess per key.