Environment variables and secrets are the configuration boundary between application code and the environment where it runs.
On an App Platform, this boundary becomes especially important because the same source revision may run in:
- preview;
- development;
- staging;
- production;
- worker;
- cron;
- one-off jobs.
The source code should remain the same while environment-specific configuration changes.
Raff Technologies Apps supports service configuration and private bindings to databases and buckets. Current product behavior includes scoped per-service credentials and private connections between services and managed databases. citeturn969325search0
Environment variables and secrets are not the same thing
An environment variable is a delivery mechanism.
The value can be:
- non-sensitive configuration;
- a secret.
Examples of normal configuration:
APP_ENV=production LOG_LEVEL=info FEATURE_CHECKOUT_V2=true
Examples of secrets:
DATABASE_PASSWORD=... STRIPE_SECRET_KEY=... SESSION_SECRET=...
Do not call every environment variable a secret.
The handling requirements differ.
Keep environment-specific values out of source code
Avoid:
const dbPassword = "production-password";
Prefer:
const dbPassword = process.env.DATABASE_PASSWORD;
This lets the same artifact run across environments without rebuilding.
It also keeps production values out of Git history.
Configuration should be explicit
A production service should have a documented list of required configuration keys.
Example:
APP_ENV PORT DATABASE_URL OBJECT_STORAGE_BUCKET EMAIL_FROM STRIPE_SECRET_KEY
At startup, validate required values.
Do not allow a missing production secret to silently fall back to a development default.
Scope secrets per service
A multi-service application should not give every process the same secret set.
For example:
web DATABASE_URL SESSION_SECRET worker DATABASE_URL QUEUE_URL EMAIL_API_KEY cron DATABASE_URL BILLING_API_KEY
The image-processing worker does not need the billing credential.
Least privilege applies inside one application stack too.
Use platform bindings when they reduce credential scope
Raff Apps can bind services to managed databases and buckets, injecting scoped per-service credentials over private connections. citeturn969325search0
This is preferable to copying a master database or bucket credential into every service.
The important security property is:
Compromising one service should not automatically expose every data resource.
Preview environments must not inherit production credentials
Preview deployments are intentionally temporary and may run code from unmerged branches.
Use separate credentials for:
- preview;
- staging;
- production.
A preview environment should not have write access to the production database merely because it was created from the same repository.
This principle also applies to third-party API keys.
Secrets need lifecycle management
A secret has a lifecycle:
create → distribute → use → rotate → revoke
For every important secret, know:
- who owns it;
- where it is configured;
- which services use it;
- how it rotates;
- how it is revoked;
- what breaks during rotation.
A secret that cannot be rotated safely is operational debt.
Rotate without forcing immediate downtime
A common rotation pattern is overlap:
- create new credential;
- allow old and new temporarily;
- update service configuration;
- deploy/restart safely;
- verify;
- revoke old credential.
This is easier when the upstream system supports multiple active credentials.
If only one credential can exist, coordinate deployment and cutover carefully.
Avoid leaking secrets into build logs
Build-time variables can appear in:
- command output;
- image layers;
- cache;
- CI logs.
Do not pass runtime secrets through Docker build arguments unless the build truly requires them and the mechanism protects them.
Prefer runtime injection.
Logs are a common secret-leak path
Bad:
console.log(process.env);
Bad:
Database connection failed: postgres://user:password@host/db
Log:
- error type;
- safe identifiers;
- operation context.
Redact:
- tokens;
- passwords;
- cookies;
- authorization headers;
- signed URLs.
Configuration changes are deployment changes
Changing a secret or environment variable can alter application behavior as much as changing source code.
Treat configuration changes with:
- ownership;
- review;
- rollout;
- verification;
- rollback.
If a new code revision requires a new variable, define compatibility between the old and new revisions.
Database URLs often bundle multiple concerns
A database URL may contain:
- hostname;
- port;
- database name;
- username;
- password;
- TLS parameters.
Do not paste it into documentation, issue trackers, or screenshots.
When the platform supplies scoped database bindings, use those instead of manually sharing one administrative URL.
Environment naming should be boring and consistent
Use a small vocabulary:
preview development staging production
Avoid one service using prod, another live, and another production unless there is a clear reason.
Consistency improves automation and incident response.
Separate environment identity from feature configuration
Do not overload APP_ENV with every behavior.
Prefer:
APP_ENV=production FEATURE_NEW_CHECKOUT=false EMAIL_MODE=live
This makes behavior explicit and reduces hidden conditional logic.
Configuration ownership in multi-service apps
For every variable, ask whether it is:
- global to the app;
- environment-specific;
- service-specific;
- secret;
- generated by a binding;
- temporary migration state.
This prevents configuration sprawl.
App Platform secret checklist
Before production:
- no production secret is committed to Git;
- required variables are validated at startup;
- preview/staging credentials are separate;
- service credentials are least privilege;
- logs redact secrets;
- database/bucket bindings are scoped;
- secret ownership is known;
- rotation procedure exists;
- configuration changes are reviewed;
- rollback compatibility is understood.
Frequently asked questions
Are environment variables secure?
Environment variables are a configuration mechanism, not a security guarantee. Sensitive values still need controlled storage, access, rotation, and logging discipline.
Should every service use the same DATABASE_URL?
Not if separate scoped credentials are available. Give each service only the database permissions it needs.
Should previews use production secrets?
No. Preview environments should use isolated low-privilege credentials and should not write to production data.
Can secrets be committed in a private Git repository?
They should not be. Repository privacy does not provide the same lifecycle, access control, rotation, and audit model as a secret-management system.
How should I rotate a secret?
Prefer overlapping old/new credentials, update the service, verify the new credential, then revoke the old one.