Bridge for gradual migration. Lets vmconfig be source of truth while legacy code still reads env vars.
Real example: Our email config has two code paths:
# NEW (vmconfig)
from_addr = vmconfig.get("email.from_addr")Here's the trick: when infra sets EMAIL_FROM_ADDR env var, vmconfig actually reads from that env var (env vars always win). The export_env bridge works in reverse too — it tells vmconfig "if someone asks for email.from_addr, also check the EMAIL_FROM_ADDR env var."
Servers were already setting EMAIL_FROM_ADDR. We couldn't migrate code and infra simultaneously. So export_env bridges them:
[vmconfig.export_env]
EMAIL_FROM_ADDR = "email.from_addr" # bidirectional bridgeThe problem if we don't finish migrating:
- Dual maintenance — values in vmconfig AND infra env vars
- Real env vars override vmconfig silently → "why isn't my change working?"
- Tech debt accumulates
1. VERIFY code uses vmconfig.get(), not os.getenv()
- Search:
grep -r "EMAIL_FROM_ADDR" src/ - Found:
emails/config.py:254still usesos.getenv("EMAIL_FROM_ADDR") - Migrate: remove that code path, keep only
vmconfig.get("email.from_addr")
2. REMOVE from [vmconfig.export_env] in vmconfig.toml
- Delete:
EMAIL_FROM_ADDR = "email.from_addr"
3. CONFIRM value exists in vmconfig.toml
- Check:
[email]from_addr = "no-reply@validmind.ai"✓
4. UPDATE vmconfig_local.toml.sample if applicable
- Not needed here (not a secret, default is fine)
5. PUSH code changes
6. SET value on server's vmconfig_local.toml (if override needed)
- Example:
[email]from_addr = "no-reply@prod.validmind.ai"
7. REMOVE env var from infra
- Delete
EMAIL_FROM_ADDRfrom Terraform/K8s secrets
End state: vmconfig = single source of truth, no env var duplication.
| Env Var | Config Key | File with legacy code |
|---|---|---|
EMAIL_SENDER |
email.provider |
emails/config.py:211 |
EMAIL_FROM_ADDR |
email.from_addr |
emails/config.py:254 |
EMAIL_FROM_NAME |
email.from_name |
emails/config.py:256 |
SMTP_SERVER |
email.smtp.server |
emails/config.py:216 |
SMTP_PORT |
email.smtp.port |
emails/config.py:220 |
SMTP_SECURITY |
email.smtp.security |
emails/config.py:226 |
SMTP_USERNAME |
email.smtp.username |
emails/config.py:221 |
POSTMARK_API_KEY |
email.postmark_api_key |
emails/config.py:264 |
SENDGRID_API_KEY |
email.sendgrid_api_key |
emails/config.py:266 |
TOKEN_CACHE_EXPIRATION_TIME_SECONDS |
api.token_cache_expiration_seconds |
auth/oidc.py:18 |
AWS_S3_KMS_KEY_ARN |
object_storage.aws.kms_key_arn |
services/object_storage.py:121 |
| Env Var | Reason |
|---|---|
DD_* |
Datadog SDK reads from os.environ |
AWS_REGION |
AWS SDK reads from os.environ |
AWS_DEFAULT_REGION |
AWS SDK reads from os.environ |
Code is already migrated. Just need to:
- Review if default value in
vmconfig.tomlis appropriate - Decide if it needs to be in
vmconfig_local.toml.sample(per-environment or secret) - Remove from
[vmconfig.export_env] - Coordinate with infra to remove env var
| Env Var | Config Key |
|---|---|
RUN_MODE |
api.run_mode |
VM_APP_URL |
api.app_url |
VM_API_URL |
api.api_url |
BACKEND_JWT_PRIVATE_KEYS |
api.jwt_private_keys |
UPDATE_THRESHOLD_HOURS |
api.update_threshold_hours |
ENABLE_SIEM_BRIDGE |
api.enable_siem_bridge |
LITELLM_URL |
litellm.url |
LITELLM_MASTER_KEY |
litellm.master_key |
REDIS_URL |
redis.url |
PG_ENCRYPTION_KEY |
database.encryption_key |
AGENTS_MODEL_ALIASES |
launchdarkly.configure.agents_model_aliases |
OBJECT_STORAGE_PROVIDER |
object_storage.provider |
VMPROJECTASSETS_NAME |
object_storage.bucket_name |
OBJECT_STORAGE_PRESIGNED_URL_EXPIRATION |
object_storage.presigned_url_expiration |
OBJECT_STORAGE_WEBHOOK_URL |
object_storage.webhook_url |
SMTP_TIMEOUT |
email.smtp.timeout |
| Env Var | Reason |
|---|---|
CASBIN_* |
Casbin is deprecated, will be removed |
VMConfig is in a transition period. We'll consider it 100% migrated when [vmconfig.export_env] only contains keys required by 3rd party libraries:
[vmconfig.export_env]
# Only these remain — external libs read them directly from os.environ
DD_TRACE_ENABLED = "datadog.trace_enabled"
DD_TRACE_LOG_FORMAT = "datadog.trace_log_format"
DD_TRACE_WRITER_LOG_LEVEL = "datadog.trace_writer_log_level"
DD_TRACE_LOG_LEVEL = "datadog.trace_log_level"
DD_LOGS_INJECTION = "datadog.logs_injection"
DD_TRACE_WRITER_INTERVAL_SECONDS = "datadog.trace_writer_interval_seconds"
DD_TRACE_WRITER_REUSE_CONNECTIONS = "datadog.trace_writer_reuse_connections"
AWS_REGION = "object_storage.aws.region"
AWS_DEFAULT_REGION = "object_storage.aws.region"Everything else: vmconfig is the single source of truth.