BTSBTS

Environment Variables

Env vars hold secrets and config your app reads at boot. BTS encrypts every value at rest with AES-256-GCM and injects them into every preview environment.

Two kinds. Variables you set (API keys, flags) and variables BTS injects automatically (DATABASE_URL, REDIS_URL, PORT, S3 credentials). Both appear in process.env.

Auto-injected variables

Set automatically when the corresponding service is enabled. Do not define these yourself.

VariableWhen it's setExample
PORTAlways3000
DATABASE_URLPostgres enabledpostgresql://user:pass@host:5432/app
REDIS_URLRedis enabledredis://host:6379
MONGODB_URLMongoDB enabledmongodb://user:pass@host:27017/app
ELASTICSEARCH_URLElasticsearch enabledhttp://host:9200
S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKETMinIO enabledhttp://host:9000, …
S3_PUBLIC_ENDPOINT, MINIO_PUBLIC_ENDPOINTMinIO enabledhttps://s3-….your-domain — the browser-reachable address. Sign presigned object URLs against this one; S3_ENDPOINT above is an in-network hostname that only resolves inside the environment, so a URL signed against it can never be fetched by a browser.
SMTP_HOST, SMTP_PORTMailHog enabledhost, 1025

Setting your own variables

Three ways to set them:

  • Dashboard — Project Settings → Environment Variables.
  • nexos.yaml — env: block. Good for non-secret config. See nexos.yaml.
  • Bulk .env import — paste or upload a .env file in the dashboard. Existing keys are updated; new keys created.

Listed values are masked — only the first four characters are shown. For real secrets, prefer the dashboard over committed YAML.

Mark credentials as secret. A project that deploys fork pull requests without secrets withholds exactly the variables marked secret — a plain variable still reaches a fork's code, whatever it holds.

Private base images

A FROM on a private registry (a private GHCR package, a company registry) needs the secret BTS_REGISTRY_AUTH, one registry per line:

ghcr.io <user>:<token>
registry.example.com:5000 <user>:<token>

BTS hands it to the node's Docker client for the build's pulls only: it never reaches a container, a build argument, a build secret or a workflow job. GHCR takes a classic token with read:packages. It is used on private nodes only: an image pulled on a shared node would stay readable to other workspaces' builds.

A pulled image stays in the node's build cache, where a Dockerfile can reach it by digest without a login. So once a node a project builds on has used these logins, fork pull requests of that project are not deployed without secrets: they get a neutral check instead, as with fork previews off. The Nodes page labels such a node. Before sharing it, moving it to another workspace, or clearing the label once you stop using the logins, archive the previews on it (their images carry the private base) and clear its build cache and images (docker builder prune -af && docker image prune -af). docker images 'nexos-*' should then list nothing.

Encryption at rest

Every value is AES-256-GCM encrypted with a unique random IV before it lands in the database. The plaintext is decrypted only at deploy time, when the value is injected into the container. The encryption key is configured on the API server via ENCRYPTION_KEY (a 32-byte hex string).

API

# Create or update
curl -X PUT https://api.test.buildingswell.com/api/projects/:id/env-vars \
  -H "Authorization: Bearer nxs_..." \
  -H "Content-Type: application/json" \
  -d '{"key": "STRIPE_SECRET_KEY", "value": "sk_test_..."}'

# List (values masked)
curl https://api.test.buildingswell.com/api/projects/:id/env-vars \
  -H "Authorization: Bearer nxs_..."

# Delete
curl -X DELETE https://api.test.buildingswell.com/api/projects/:id/env-vars/:varId \
  -H "Authorization: Bearer nxs_..."