Secrets and Variables
BRIDGEPORT stores your configuration values — encrypted secrets (API keys, passwords, tokens) or plaintext variables (hostnames, ports, feature flags) — and makes them available to services through config file templates with automatic ${KEY} placeholder substitution.
Two entities, one resolution pipeline:
- Secrets — encrypted at rest (AES-256-GCM), access-controlled, audit-logged. For sensitive values.
- Variables (Vars) — stored as plaintext, no reveal restrictions. For non-sensitive values you still want to centralize and reuse across files.
Both are environment-scoped and resolve the same ${KEY} placeholder syntax. If the same key exists as both a var and a secret, the secret wins.
Quick Start
Section titled “Quick Start”Store a secret and use it in a config file in under a minute:
- Go to Configuration > Secrets in the sidebar.
- Click Add Secret.
- Enter key
DATABASE_URL, valuepostgres://user:pass@db:5432/app, and click Create. - Go to Configuration > Config Files, create or edit a
.envfile. - Add
DATABASE_URL=${DATABASE_URL}to the file content. - Attach the config file to a service, then Sync Files — BRIDGEPORT writes the resolved value to the server.
How It Works
Section titled “How It Works”Secrets and vars in BRIDGEPORT follow a simple flow: store (encrypted for secrets, plaintext for vars), reference by name, resolve at sync time.
flowchart LR
S[Secret<br/>key: DATABASE_URL] --> E[Encrypted with<br/>AES-256-GCM]
V[Var<br/>key: LOG_LEVEL] --> P[Plaintext]
E --> DB[(Database)]
P --> DB
CF[Config file template<br/>with ${KEY} placeholders] --> Sync[Sync to server]
DB --> Sync
Sync --> Server[Server file<br/>resolved values]
Key concepts:
- Environment-scoped. Both secrets and vars belong to an environment. A
DATABASE_URLin staging is independent from one in production. Keys must be unique within(environment, entity)— you can haveFOOas both a secret and a var in the same environment, but not two secrets namedFOO. - Encryption only for secrets. Secret values are encrypted using AES-256-GCM with the
MASTER_KEYbefore being stored; the nonce is stored alongside the ciphertext. Vars are stored as plaintext. - Audit-logged. Secret access (reveal, sync, update) is recorded in the audit log. Var create/update/delete are also audited.
- Template-based usage. Neither secrets nor vars are injected at runtime. They are substituted into config file templates at sync time and written as static files to the server.
- Resolution order. Vars are substituted first, then secrets. If the same key exists in both, the secret value wins.
Creating Secrets
Section titled “Creating Secrets”Via the UI
Section titled “Via the UI”- Navigate to Configuration > Secrets.
- Click Add Secret.
- Fill in the form:
| Field | Required | Description |
|---|---|---|
| Key | Yes | Uppercase name with underscores (e.g., DATABASE_URL). Must match ^[A-Z][A-Z0-9_]*$. |
| Value | Yes | The secret value. Can be any string. |
| Description | No | Optional description for documentation. |
| Write-Only | No | When enabled, the value can never be revealed after creation. See Reveal Controls. |
- Click Create.
Via the API
Section titled “Via the API”POST /api/environments/:envId/secretsAuthorization: Bearer <token>Content-Type: application/json
{ "key": "DATABASE_URL", "value": "postgres://user:pass@db-host:5432/appdb", "description": "Primary database connection string", "neverReveal": false}Response (200):
{ "secret": { "id": "clxyz...", "key": "DATABASE_URL", "description": "Primary database connection string", "neverReveal": false, "createdAt": "2026-02-25T10:00:00.000Z", "updatedAt": "2026-02-25T10:00:00.000Z" }}Creating Variables
Section titled “Creating Variables”Variables live on the Vars tab of the Secrets and Variables page. Use them for non-sensitive values you still want to centralize — log levels, feature flags, hostnames, ports — so the same value doesn’t get duplicated across config files.
Via the UI
Section titled “Via the UI”- Navigate to Configuration > Secrets and Variables.
- Switch to the Vars tab.
- Click Add Variable.
- Fill in the form:
| Field | Required | Description |
|---|---|---|
| Key | Yes | Uppercase name with underscores (e.g., LOG_LEVEL). Must match ^[A-Z][A-Z0-9_]*$. |
| Value | Yes | The variable value. Any string. |
| Description | No | Optional description for documentation. |
- Click Create.
Via the API
Section titled “Via the API”POST /api/environments/:envId/varsAuthorization: Bearer <token>Content-Type: application/json
{ "key": "LOG_LEVEL", "value": "info", "description": "Application log level"}PATCH /api/vars/:idDELETE /api/vars/:idWhen to Use a Var Instead of a Secret
Section titled “When to Use a Var Instead of a Secret”| Use a Var for | Use a Secret for |
|---|---|
| Log levels, feature flags | API keys, tokens |
| Public hostnames, URLs | Passwords, private keys |
| Ports, timeouts | Certificates, signing keys |
| Docker image tags | Database connection strings with credentials |
If in doubt, prefer a secret — it costs nothing extra and preserves future flexibility.
Using Placeholders in Config Files
Section titled “Using Placeholders in Config Files”Secrets and vars come to life when you reference them in config files. The standard placeholder syntax is ${KEY} — the same for both.
Example: .env File
Section titled “Example: .env File”Create a config file with this content:
# Application configurationDATABASE_URL=${DATABASE_URL}REDIS_URL=${REDIS_URL}API_KEY=${API_KEY}DEBUG=falseLOG_LEVEL=infoWhen you sync this file to a server, BRIDGEPORT resolves each ${KEY} placeholder with the corresponding secret value from the environment. The file written to the server contains the actual values:
# Application configurationDATABASE_URL=postgres://user:pass@db-host:5432/appdbREDIS_URL=redis://redis-host:6379/0API_KEY=sk-live-abc123def456DEBUG=falseLOG_LEVEL=infoPlaceholder Syntax
Section titled “Placeholder Syntax”BRIDGEPORT recognizes the following placeholder formats when listing secret usage:
| Format | Example | Used For |
|---|---|---|
${KEY} | ${DATABASE_URL} | Standard format, resolved during sync |
$KEY | $DATABASE_URL | Detected for usage tracking |
{{KEY}} | {{DATABASE_URL}} | Detected for usage tracking |
Only the ${KEY} format is resolved during config file sync. The $KEY and {{KEY}} formats are used for usage tracking only — they will not be substituted with secret values when syncing files to servers.
Missing Keys
Section titled “Missing Keys”If a config file references a key that does not exist as either a secret or a var in the environment, the sync fails with an error listing the missing keys. This is a safety measure — BRIDGEPORT will not write a file with unresolved placeholders to your server. Create the missing secret or var first, then retry the sync.
Template Flow
Section titled “Template Flow”flowchart TD
A[Config file template<br/>stored in BRIDGEPORT] --> B[Sync triggered<br/>per-service or per-server]
B --> C[Fetch vars + decrypted secrets<br/>for the environment]
C --> D[Substitute vars first,<br/>then secrets over them]
D --> E{Any unresolved<br/>placeholders?}
E -->|Yes| F[Sync fails<br/>lists missing keys]
E -->|No| G[Write resolved file<br/>to target path via SSH]
G --> H[Update lastSyncedAt<br/>timestamp]
Config File Scanner
Section titled “Config File Scanner”The Config File Scanner inspects every non-binary config file in the environment and flags two categories of issues:
Hardcoded values to extract:
- Cross-file repetition — the same literal value appears in two or more files (classic duplication — rotate one, forget the other).
- Plaintext leaks — the literal value matches an existing secret or var, meaning the plaintext is sitting in a config file it shouldn’t be.
The scanner only inspects values from env-style KEY=value lines and UPPER_SNAKE_CASE YAML entries (KEY: value). Docker-compose attribute keys like restart, image, or command are skipped so their values (e.g. unless-stopped) aren’t mistaken for config variables. Same value under different keys (e.g. three CADDY_*_BACKEND settings pointing at the same host) is treated as three separate settings, not one shared variable — the scanner won’t merge them.
Missing references:
- Undefined
${KEY}references — a file uses${SOMETHING}(including${SOMETHING:-default}) but no matching secret or var is defined in the environment. These are listed first in the results so they’re easy to spot.
Binary files are skipped. The scan runs on demand; nothing is scanned or stored automatically.
Running a Scan
Section titled “Running a Scan”- Navigate to Configuration > Secrets and Variables.
- Expand the Scan Suggestions panel (or click Run Scan).
- BRIDGEPORT reports all suggestions sorted by occurrence count (highest first), with secret-looking values ranked above var-looking ones on ties.
Each suggestion includes:
- A
kindof eitherhardcoded_value(extract this literal) ormissing_reference(referenced but undefined). - A proposed key derived from the most common key name seen (or the referenced key, for missing references), normalized to
UPPER_SNAKE_CASE. - A proposed type (
secretorvar). Keys containingpassword,secret,key,token,api,auth,credential,cert, orprivateare classified as secrets; everything else as vars. - The affected files and per-file occurrence counts.
- For
hardcoded_value, the actual literal value, prefilled into the Confirm dialog so you don’t have to retype it. Formissing_reference, the value is empty and you provide it. - If a hardcoded value matches an existing secret/var, a pointer to it so you can reuse the existing key rather than creating a duplicate.
Review and Apply
Section titled “Review and Apply”The apply flow depends on the suggestion kind.
Hardcoded values walk through a 3-step modal:
- Confirm — review the proposed key, type, and affected files. The value is prefilled (editable). Edit the key/type before proceeding.
- Preview — see a per-file diff showing only the changed lines (
literal-value→${KEY}). Each file is a collapsible section so multi-file changes stay manageable. Substitution only replaces lines where the value is the entire right-hand side of aKEY=value(orKEY: value) pair — substring occurrences inside other values (e.g.staginginsideapp-staging.bridgein.com) are intentionally left alone. Nothing is written yet. - Apply — BRIDGEPORT creates the secret or var (if not already present), substitutes the value in every selected file, saves the previous content to file history for rollback, and writes an audit log entry per mutation with
source: config_scan.
Missing references skip the preview step (no file modifications happen):
- Define — review the key/type and enter the value.
- Create — BRIDGEPORT creates the secret or var. The referencing files are unchanged; they already use
${KEY}.
The scan itself is read-only and always safe to run. Only the apply step mutates data.
Tuning the Scanner
Section titled “Tuning the Scanner”Two per-environment settings control sensitivity (both are in Settings > Configuration, group “Config Scanner”):
| Setting | Default | Description |
|---|---|---|
scanMinLength | 6 | Minimum value length to consider. Shorter values are ignored. Valid range 1—100. |
scanEntropyThreshold | 25 | Shannon entropy threshold, stored as ×10 (25 = 2.5 bits/char). Values below this are filtered out. Valid range 0—80. |
Raise scanEntropyThreshold if you’re getting too many suggestions for low-entropy strings (short words, common defaults). Lower it if real secrets are being missed.
Values that already look like placeholders (start with ${, $, or {{) are never suggested.
POST /api/environments/:envId/config-scanAuthorization: Bearer <token>Returns { suggestions, scannedFileCount, skippedBinaryCount }. Each suggestion includes kind (hardcoded_value or missing_reference), value (raw plaintext for hardcoded values; empty string for missing references), proposedKey, proposedType, affectedFiles, occurrenceCount, and optional existingSecretId/existingSecretKey.
POST /api/environments/:envId/config-scan/previewPOST /api/environments/:envId/config-scan/applyAuthorization: Bearer <token>Content-Type: application/json
{ "value": "the-literal-value-to-replace", "key": "MY_KEY", "type": "secret", "fileIds": ["cf1", "cf2"], "existingSecretId": null}The preview response is { diffs: [{ fileId, fileName, replacements, hunks: [{ lineNumber, before, after }] }] } — only the lines that actually changed.
Set existingSecretId to reuse an existing secret or var instead of creating a new one. For missing_reference applies, pass an empty fileIds array — no files are modified.
Reveal Controls
Section titled “Reveal Controls”BRIDGEPORT has three independent layers of reveal control to protect sensitive values. All three must permit a reveal for GET /api/secrets/:id/value to return the plaintext.
Role Requirement (Admin-Only)
Section titled “Role Requirement (Admin-Only)”Revealing a secret value requires the admin role. Operators and viewers receive 403 Forbidden (code: FORBIDDEN_ROLE) from GET /api/secrets/:id/value, and the reveal control is hidden for them in the UI.
- Operators can still create, update, and use secrets in config-file syncs — they just never see the current plaintext.
- This is enforced server-side, so it also applies to API tokens: a non-admin token cannot reveal values regardless of its environment scope.
Environment-Level Control
Section titled “Environment-Level Control”Admins can disable secret reveal for an entire environment:
Location: Settings > Configuration > “Allow Secret Reveal”
When disabled:
- No secret values in that environment can be revealed through the UI or API.
- API calls to
GET /api/secrets/:id/valuereturn403 Forbiddenwith the message “Secret reveal is disabled for this environment”. - Secrets can still be updated and used in config file syncs — only revealing is blocked.
- The audit log records blocked reveal attempts.
Secret-Level Control (Write-Only)
Section titled “Secret-Level Control (Write-Only)”Individual secrets can be marked as write-only using the neverReveal flag:
When enabled:
- The secret value can never be revealed through the UI or API, regardless of environment settings.
- API calls to
GET /api/secrets/:id/valuereturn403 Forbiddenwith “This secret is write-only and cannot be revealed”. - The value can still be updated (you can set a new value without seeing the old one).
- The value is still resolved during config file syncs.
When to use write-only:
- Production database root passwords
- Third-party API keys that should never be displayed
- Signing keys and certificates that are set once and never read back
POST /api/environments/:envId/secretsAuthorization: Bearer <token>Content-Type: application/json
{ "key": "STRIPE_SECRET_KEY", "value": "sk_live_abc123...", "neverReveal": true}The neverReveal flag cannot be reversed through the API. Once a secret is write-only, the only way to change this is to delete the secret and recreate it. Treat this as a one-way operation.
Usage Tracking
Section titled “Usage Tracking”The Secrets and Vars tabs both show where each key is referenced, so you can understand the impact of changing or deleting an entry before you do it.
For every secret and var, BRIDGEPORT reports:
- Config files that reference the key (by detecting
${KEY},$KEY,{{KEY}}, or a leadingKEY=line). - Services that those config files are attached to.
- Usage count — the number of unique services using the key.
Usage is maintained by the SecretUsage / VarUsage join tables: BRIDGEPORT extracts the referenced keys whenever a config file’s content changes (create, update, restore-from-history, scan-apply, asset upload) and updates the matching rows. The list endpoints answer “what’s using this key?” with a join, not a per-request content scan — so the cost scales with the number of usages, not the size of every config file in the environment.
GET /api/environments/:envId/secretsGET /api/environments/:envId/varsAuthorization: Bearer <token>Each entry in the response includes:
{ "key": "DATABASE_URL", "usageCount": 3, "usedByConfigFiles": [ { "id": "cf1", "name": "App API .env", "filename": ".env", "services": [ { "id": "svc1", "name": "app-api", "serverName": "server-1" }, { "id": "svc2", "name": "app-api", "serverName": "server-2" } ] } ], "usedByServices": [ { "id": "svc1", "name": "app-api", "serverName": "server-1" }, { "id": "svc2", "name": "app-api", "serverName": "server-2" } ]}Updating and Rotating
Section titled “Updating and Rotating”Updating a Value
Section titled “Updating a Value”UI: Click the edit icon on a secret in the list, enter the new value, click Save.
API:
PATCH /api/secrets/:idAuthorization: Bearer <token>Content-Type: application/json
{ "value": "postgres://newuser:newpass@db-host:5432/appdb"}Updating a secret re-encrypts the value with the current MASTER_KEY. Vars use PATCH /api/vars/:id with the same { value?, description? } shape and are updated in place. After updating either, config files that reference the key will show a “pending” sync status until re-synced.
Rotation Workflow
Section titled “Rotation Workflow”When rotating a secret (e.g., a database password):
- Update the secret value in BRIDGEPORT.
- Check which services use the secret (via usage tracking).
- Sync config files to all affected services.
- Restart the services to pick up the new configuration.
Best Practices
Section titled “Best Practices”Naming Conventions
Section titled “Naming Conventions”Use consistent, descriptive key names:
| Pattern | Examples | Use For |
|---|---|---|
SERVICE_SETTING | DATABASE_URL, REDIS_URL | Connection strings |
PROVIDER_KEY | STRIPE_API_KEY, SENDGRID_KEY | Third-party API keys |
APP_SECRET | DJANGO_SECRET_KEY, JWT_SECRET | Application secrets |
Access Control
Section titled “Access Control”- Disable reveal for production. Set “Allow Secret Reveal” to false in production environment settings.
- Use write-only for critical secrets. Mark database root passwords, signing keys, and master API keys as
neverReveal. - Audit regularly. Review the audit log for secret access patterns. Unexpected reveals may indicate a security concern.
Operational Hygiene
Section titled “Operational Hygiene”- One secret per value. Do not pack multiple values into a single secret. Use
DB_HOST,DB_PORT,DB_PASSWORDinstead of a JSON blob. - Document with descriptions. Use the description field to note what the secret is for, who owns it, and when it was last rotated.
- Check usage before deleting. The usage tracking feature shows all config files and services that depend on a secret. Deleting a secret that is still referenced will cause sync failures.
- Keep secrets environment-specific. Even if staging and production use the same API key, store it separately in each environment for isolation.
Configuration Options
Section titled “Configuration Options”Secret Fields
Section titled “Secret Fields”| Field | Type | Default | Description |
|---|---|---|---|
key | string | — | Uppercase key name (unique per environment) |
encryptedValue | string | — | AES-256-GCM encrypted value |
nonce | string | — | Encryption nonce (base64) |
description | string | null | Optional documentation |
neverReveal | boolean | false | Write-only flag |
Var Fields
Section titled “Var Fields”| Field | Type | Default | Description |
|---|---|---|---|
key | string | — | Uppercase key name (unique per environment) |
value | string | — | Plaintext value |
description | string | null | Optional documentation |
Related Environment Settings
Section titled “Related Environment Settings”| Setting | Location | Default | Description |
|---|---|---|---|
allowSecretReveal | Settings > Configuration > Security | true | Environment-level reveal toggle (admin only) |
scanMinLength | Settings > Configuration > Config Scanner | 6 | Minimum value length considered by the scanner |
scanEntropyThreshold | Settings > Configuration > Config Scanner | 25 | Shannon entropy threshold ×10 (25 = 2.5 bits/char) |
Related Environment Variables
Section titled “Related Environment Variables”| Variable | Default | Description |
|---|---|---|
MASTER_KEY | — (required) | 32-byte base64 key used for all encryption/decryption |
Troubleshooting
Section titled “Troubleshooting”“Secret already exists” Secret keys must be unique within an environment. Check the existing secrets list for the key. If you need to change the value, update the existing secret instead of creating a new one.
“Key must be uppercase with underscores”
Secret keys must match the pattern ^[A-Z][A-Z0-9_]*$. Examples of valid keys: DATABASE_URL, API_KEY, STRIPE_SECRET_KEY. Examples of invalid keys: database_url, api-key, 123_KEY.
“Secret reveal is disabled for this environment” An admin has disabled secret reveal for this environment. Secrets can still be updated and used in syncs. If you need to reveal a value, ask an admin to temporarily enable the setting at Settings > Configuration.
“This secret is write-only and cannot be revealed”
The secret has neverReveal: true. This cannot be reversed. If you need to see the value, delete the secret and recreate it without the write-only flag.
“Missing secrets: KEY1, KEY2” during config file sync The config file references keys that exist as neither a secret nor a var in this environment. Create the missing entry (as a secret if sensitive, as a var otherwise), then retry the sync.
Secret value not updating on the server after change Updating a secret or var in BRIDGEPORT does not automatically sync config files. After updating, re-sync the config files attached to the affected services. The sync status will show “pending” for files that need re-syncing.
“Var already exists” Var keys must be unique within an environment (same rule as secrets, but tracked on a separate table). The same key can exist as both a secret and a var in one environment — the secret takes precedence at resolution time.
Config scanner returns no suggestions
The scanner only surfaces values that (a) meet scanMinLength, (b) exceed scanEntropyThreshold, and (c) appear in multiple files, under multiple keys, or match an existing secret/var. If nothing qualifies, the panel stays empty. Lower the entropy threshold in Settings > Configuration to catch more candidates.
Related
Section titled “Related”- Config Files — Using
${KEY}placeholders in config file templates - Environment Settings — The
allowSecretRevealtoggle - Audit Logs — Tracking secret access
- Configuration Reference —
MASTER_KEYenvironment variable