Environment Configuration
Environment Configuration
Project: Drop{{PROJECT_NAME}}
Version: 0.1.0{{VERSION}}
Date: 2026-02-23{{DATE}}
Author: Platform Architect (AI){{AUTHOR}}
Status: Draft | In Review | Approved
Reviewers: Alem Bašić (CEO){{REVIEWERS}}
Document History
| Version |
Date |
Author |
Changes |
| 0.1 |
2026-02-23{{DATE}} |
Platform Architect (AI){{AUTHOR}} |
Initial draft from source code analysis |
1. Environment Overview
| Environment |
Purpose |
URL |
Access |
Managed By |
| Local |
Developer workstation |
http://localhost:3000localhost |
Developer |
Individual |
| Dev |
Integration, daily builds |
dev.{{DOMAIN}} |
Team + CI |
Platform team |
| Staging |
Pre-production validation |
drop-staging.fly.dev{{DOMAIN}} (Fly.io Stockholm) |
Team + CIQA + PM |
AlemPlatform Bašićteam |
| Production |
Live system |
https://9ef3szvvsb.eu-west-1.awsapprunner.com{{DOMAIN}} (future: getdrop.no) |
Alem BašićOps only |
AlemPlatform Bašićteam |
| Preview |
Feature branch review |
{{BRANCH}}.preview.{{DOMAIN}} |
Team + Stakeholders |
CI/CD |
2. Per-Environment Configuration
2.1 Development Environment
Parameter |
Value |
Notes |
|---|
NODE_ENV |
development |
Auto-seeds demo user ([email protected] / demo1234) |
Database |
SQLite at ./drop.db (project root) |
Auto-detected when no DATABASE_URL set |
Auth |
Email/password login (legacy, deprecated in prod) OR BANKID_MOCK=true |
BankID mock skips OIDC flow |
Service mode |
NEXT_PUBLIC_SERVICE_MODE=mock |
No real external API calls |
Rate limiting |
Enabled (in-memory fallback when no DB table) |
Lower limits in dev are OK |
JWT_SECRET |
Dev fallback (hash of process.cwd()) |
Fatal error if missing in production |
Demo user |
Auto-seeded |
Login at http://localhost:3000 with demo credentials |
Security headers |
CSP with unsafe-eval + unsafe-inline |
Required for Next.js HMR |
Cookies |
secure: false |
No HTTPS in local dev |
2.2 Staging Environment
| Parameter |
Value |
Notes |
PlatformLog level |
Fly.io, region arnDEBUG (Stockholm) |
AppVerbose name:logging drop-stagingfor development |
| Database |
SQLite ephemeral volume at /app/data/drop.dbdev-db.{{INTERNAL_DOMAIN}} |
NoShared automateddev backupDB, refreshed weekly |
| Cache |
NODE_ENVdev-redis.{{INTERNAL_DOMAIN}} |
production | Shared NoRedis, demono seed datapersistence |
NEXT_PUBLIC_SERVICE_MODEEmail |
mockMailtrap / fake SMTP |
ExternalEmails servicesnot mockeddelivered to real recipients |
BANKID_MOCKPayments |
trueSandbox / test mode |
MockNo BankIDreal OIDCtransactions |
flow
| Feature flags |
All enabled |
Developers can test unreleased features |
| Debug tools |
Enabled |
Profiler, debug toolbar, etc. |
| Rate limiting |
Disabled |
Developer convenience |
Auto-scalingmigrations |
ScaleEnabled |
to 0 idle, auto-startRuns on request |
|
SEED_DEMO |
Optional — set to seed test data |
|
Storage |
Persistent Fly.io volume drop_data at /app/data |
|
Secrets |
Fly.io secrets (fly secrets set KEY=VALUE) |
startup |
Intentional staging/production differences:
External payment/banking services: mocked (not real BankID or bank connections)
Scaling: scales to zero (production does not)
Database: SQLite (production is PostgreSQL RDS)
2.32 ProductionStaging Environment
| Parameter |
Value |
Notes |
PlatformLog level |
AWS App Runner (eu-west-1) |
Service ARN: arn:aws:apprunner:eu-west-1:324480209768:service/drop-web/8e45b0d335304487a1880f4e32d6aeec |
NODE_ENVINFO |
Same as production |
No demo seed, demo credentials disabled |
| Database |
RDS PostgreSQL 16 at drop-staging-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432{{INTERNAL_DOMAIN}} |
DB:Isolated dropapp,staging user:DB, dropuserproduction-scale |
| Cache |
DATABASE_URLstaging-redis.{{INTERNAL_DOMAIN}} |
postgresql://dropuser:<password>@drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432/dropapp | Dedicated From AWS Secrets ManagerRedis |
| Email |
JWT_SECRETstaging@{{DOMAIN}} |
MinSends 32to chars,internal storedtest ininboxes AWS Secrets Manager |
Fatal startup error if missingonly |
NEXT_PUBLIC_SERVICE_MODEPayments |
productionSandbox / test mode |
RealNo externalreal service callstransactions |
BANKID_MOCKFeature flags |
NotMirrors setproduction (false) | + Realstaged BankID OIDC |
SLACK_WEBHOOK_URL |
From AWS Secrets Manager |
Operational alerts to #drop-ops |
Security headers |
Full CSP (no unsafe-eval), HSTS max-age=63072000; includeSubDomains; preloadfeatures |
|
CookiesDebug tools |
secure: true, httpOnly: true, sameSite: strictDisabled |
Must match production behavior |
| Rate limiting |
Persistent (DB-backed via rate_limits table)Enabled |
10/minSame auth,limits 120/minas publicproduction |
Auto-scalingData refresh |
AWSWeekly Appfrom Runnerproduction managed(anonymized) |
MinimumSee data refresh runbook |
Intentional staging/production differences:
- Email delivery: internal only (not real users)
- Payment: sandbox (not real transactions)
- Data: anonymized copies (not real PII)
2.3 Production Environment
| Parameter |
Value |
Notes |
| Log level |
WARN |
Errors and warnings only |
| Database |
{{PROD_DB_HOST}} |
See secrets manager |
| Cache |
{{PROD_REDIS_HOST}} |
Clustered Redis |
| Email |
{{EMAIL_PROVIDER}} |
Real delivery via SES/Sendgrid/etc. |
| Payments |
Live mode |
Real transactions |
| Feature flags |
Conservative — tested features only |
New features behind flags |
| Debug tools |
Disabled |
Security requirement |
| Rate limiting |
Enabled |
See rate limit table |
| HSTS |
Enabled (1 instanceyear, includeSubDomains) |
|
| CSP |
Strict |
See security headers config |
2.4 Preview / Feature Environments
Trigger: Pull request opened against main / develop
Lifetime: Active while PR is open; destroyed on PR close
URL Pattern: {{BRANCH_SLUG}}.preview.{{DOMAIN}}
Database: Ephemeral copy (seeded from fixture data, not production)
Teardown: Automated — triggered by PR close webhook
| Parameter |
Value |
| Log level |
DEBUG |
| Email |
Fake SMTP / preview inbox |
| Payments |
Sandbox |
| Feature flags |
Branch-specific flags enabled |
3. Environment Variables Reference
| Variable |
Description |
Required |
Default |
Sensitive |
Environments |
NODE_ENV |
Runtime environment |
Yes |
development |
No |
All |
PORT |
HTTP server port |
NoYes |
3000 |
No |
All |
HOSTNAME |
Server bind address |
No |
0.0.0.0 |
No |
All |
DATABASE_URL |
PostgreSQL connection string |
Yes |
— |
Yes |
All |
REDIS_URL |
Redis connection string |
Yes |
redis://localhost:6379 |
Yes |
All |
JWT_SECRET |
JWT signing key |
Yes |
— |
Yes |
All |
JWT_EXPIRY |
Token expiry duration |
Yes |
1h |
No |
(SQLiteAll |
if
absent)
SMTP_HOST |
SMTP server hostname |
Yes |
— |
No |
All |
SMTP_USER |
SMTP username |
Yes |
— |
Yes |
All |
SMTP_PASS |
SMTP password |
Yes |
— |
Yes |
All |
S3_BUCKET |
Object storage bucket name |
Yes |
— |
No |
All |
AWS_REGION |
Cloud region |
Yes |
eu-west-1 |
No |
All |
SENTRY_DSN |
Error tracking DSN |
No |
— |
Yes |
Staging, Prod |
JWT_SECRETSTRIPE_KEY |
JWTPayment signingAPI key (min 32 chars) |
Yes (prod)if payments) |
Dev: process.cwd() hash— |
Yes |
All |
JWT_ALGORITHMLOG_LEVEL |
HS256Logging or RS256verbosity |
No |
HS256info |
No |
All |
JWT_EXPIRYRATE_LIMIT_WINDOW |
TokenRate expirylimit window (web)ms) |
No |
24h60000 |
No |
All |
NEXT_PUBLIC_SERVICE_MODERATE_LIMIT_MAX |
mockMax orrequests productionper window |
No |
mock100 |
No |
All |
NEXT_PUBLIC_APP_URLFEATURE_FLAG_KEY |
AppFeature URLflag forSDK CSRF origin check |
No |
— |
No |
Staging, Prod |
SLACK_WEBHOOK_URL |
Slack incoming webhook for alerts |
Yes (prod) |
— |
Yes |
Staging, Prod |
BANKID_CLIENT_ID |
BankID OIDC client ID |
Yes (prod) |
— |
No |
Staging, Prod |
BANKID_CLIENT_SECRET |
BankID OIDC client secret |
Yes (prod) |
— |
Yes |
Staging, Prod |
BANKID_CALLBACK_URL |
BankID web callback URL |
Yes (prod) |
— |
No |
Staging, Prod |
BANKID_CALLBACK_URL_MOBILE |
BankID mobile deep link |
Yes (mobile) |
— |
No |
Staging, Prod |
BANKID_MOCK |
Skip real BankID OIDC (true) |
No |
false |
No |
Local, Staging |
SUMSUB_API_URL |
Sumsub KYC API endpoint |
No |
https://api.sumsub.com |
No |
All |
SUMSUB_APP_TOKEN |
Sumsub API token |
Yes (prod) |
— |
Yes |
Staging, Prod |
SUMSUB_SECRET_KEY |
Sumsub webhook secret |
Yes (prod) |
— |
Yes |
Staging, Prod |
DOPPLER_TOKEN |
Doppler secrets provider tokenkey |
No |
— |
Yes |
Optional |
AWS_SECRET_ARN |
AWS Secrets Manager ARN |
No |
— |
Yes |
Prod (optional) |
SEED_DEMO |
Seed demo data in staging |
No |
— |
No |
Staging |
NEXT_PUBLIC_FF_NOTIFICATIONS |
Enable notifications feature |
No |
true |
No |
All |
NEXT_PUBLIC_FF_MERCHANT_DASHBOARD |
Enable merchant dashboard |
No |
true |
No |
All |
NEXT_PUBLIC_FF_VIRTUAL_CARDS |
Enable virtual cards (future) |
No |
false |
No |
All |
NEXT_PUBLIC_FF_PHYSICAL_CARDS |
Enable physical cards (future) |
No |
false |
No |
All |
NEXT_PUBLIC_FF_CARD_DETAILS |
Enable card details (future) |
No |
false |
No |
All |
NEXT_PUBLIC_FF_CARD_FREEZE |
Enable card freeze (future) |
No |
false |
No |
All |
NEXT_PUBLIC_FF_CARD_PIN |
Enable card PIN (future) |
No |
false |
No |
All |
NEXT_PUBLIC_FF_SPENDING_LIMITS |
Enable spending limits (future) |
No |
false |
No |
All |
Rules:
- Sensitive variables MUST be sourced from
AWS{{SECRET_STORE}} Secretsin Managerstaging (production)and or Fly.io secrets (staging)production
- Never commit sensitive values to source control
- Use
.env.localexample with placeholder values for localdeveloper development (never committed — in .gitignore)onboarding
- Rotate all secrets on team member offboarding
4. Secrets Management
4.1 Secret Storage Solution
Drop uses an abstracted secrets management system (src/lib/secrets.ts) with auto-detected provider:
Priority:Solution: DOPPLER_TOKEN{{SECRET_TOOL}} set → Doppler | AWS_SECRET_ARN set → AWS Secrets Manager | default → process.env
| Environment |
Secret Store |
Access Method |
| Local |
.env.localenv file (never committed) |
Developer managed |
| Dev |
{{DEV_SECRET_STORE}} |
CI/CD service account |
| Staging |
Fly.io secrets (fly secrets set){{STG_SECRET_STORE}} |
AlemIAM Bašićrole / CIservice account |
| Production |
AWS Secrets Manager + App Runner env{{PROD_SECRET_STORE}} |
App Runner IAM role / service account |
Cache TTL: 5 minutes (in-memory, resets on restart)
4.2 Secret Rotation Schedule
| Secret Type |
Rotation Schedule |
Automated |
Owner |
JWT_SECRETDatabase passwords |
90 days |
No (manual){{AUTOMATED}} |
AlemPlatform Bašićteam |
DATABASE_URLAPI /keys PostgreSQL credentials(internal) |
90365 days |
No (manual) |
AlemService Bašićowner |
BANKID_CLIENT_SECRETAPI keys (third-party) |
Per BankID policy / onOn compromise |
No |
AlemDev Bašićlead |
SUMSUB_APP_TOKENJWT signing keys |
180365 days |
No |
AlemPlatform Bašić |
SLACK_WEBHOOK_URL |
On compromise only |
No |
Alem Bašićteam |
| TLS certificates |
Auto-renewed60 bydays Appbefore Runner / Fly.ioexpiry |
Yes{{AUTOMATED}} |
AWSPlatform / Fly.ioteam |
Rotation impact of JWT_SECRET: Invalidates all active user sessions — users must re-authenticate.
Generate new JWT secret: openssl rand -base64 48
4.3 Access Controls
| Role |
LocalDev Secrets |
Staging Secrets |
Production Secrets |
AlemDeveloper |
BašićRead/Write |
(CEO)Read |
No access |
| DevOps |
Read/Write |
Read/Write |
Read/Write |
AI DirectorCI/CD (John)build) |
Read |
Read |
No access |
Via tooling only |
Via tooling only |
CI/CD (GitHub Actions)deploy) |
No access |
Read (via OIDC) |
Read (deploy role) |
| Application runtime |
Read (process.env)scoped) |
Read (Fly.io secrets)scoped) |
Read (App Runner env / Secrets Manager)scoped) |
5. Feature Flags Per Environment
Tool: Environment variables (NEXT_PUBLIC_FF_* baked at build time){{FF_TOOL}}
| Flag |
LocalDev |
Staging |
Production |
Notes |
notificationsfeature-new-checkout |
trueOn |
trueOn |
trueOff |
EnabledWaiting for QA sign-off |
merchantDashboardfeature-dark-mode |
trueOn |
trueOn |
trueOff |
EnabledRollout planned {{DATE}} |
virtualCardskill-switch-payments |
falseOff |
falseOff |
falseOff |
FutureEmergency —disable requires card issuing partneronly |
physicalCardsmaintenance-mode |
falseOff |
falseOff |
falseOff |
Future | Emergency
cardDetails |
false |
false |
false |
Future |
cardFreeze |
false |
false |
false |
Future |
cardPin |
false |
false |
false |
Future |
spendingLimits |
false |
false |
false |
Futureonly |
6. Database Configuration Per Environment
| Parameter |
Local |
Dev |
Staging |
Production |
Engine |
SQLite (better-sqlite3) |
SQLite (better-sqlite3) |
PostgreSQL 16 |
Host |
./drop.dblocalhost (project root) |
/app/data/drop.db{{DEV_DB}} (Fly volume) |
drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com{{STG_DB}} |
{{PROD_DB}} |
| Port |
—5432 |
—5432 |
5432 |
5432 |
| Database name |
— |
—{{APP}}_dev |
dropapp{{APP}}_dev |
{{APP}}_staging |
{{APP}}_prod |
Username | Max — |
—connections |
dropuser10 |
25 |
50 |
{{PROD_CONNS}} |
| SSL required |
No |
No |
Yes (RDS default) |
Max connections |
10 (SQLite) |
10 (SQLite) |
~85 (db.t4g.micro default)Yes |
| Connection pool |
No |
No |
TBDYes (PgBouncer{{POOL}}) |
planned)Yes ({{POOL}}) |
| Read replica |
No |
No |
No |
Yes |
| Backup |
No |
Manual sqlite3 exportDaily |
Automated RDS snapshot, daily at 23:24 UTC, 7-day retention |
Point-in-Time RecoveryDaily |
No |
No |
Yes (5-min granularity){{BACKUP_FREQ}} |
7. External Service Configuration Per Environment
| Service |
LocalDev |
Staging |
Production |
Notes |
BankIDEmail OIDC(SMTP) |
Mock (BANKID_MOCK=true)Mailtrap |
MockMailtrap |
LiveSendGrid (Norwegian/ BankID)SES |
Mock skips full OIDC flow |
Sumsub KYCPayments |
MockStripe (90% approval, 3s delay)test |
MockStripe test |
LiveStripe (api.sumsub.com)live |
OnlyDifferent production-readyAPI external servicekeys |
Open Banking (AISP/PISP)SMS |
MockTwilio (Swan mock deprecated)test |
MockTwilio test |
TBDTwilio providerlive |
Provider selection pending |
Slack alertsAnalytics |
Console log only (no webhook)Disabled |
SLACK_WEBHOOK_URLStaging requiredproperty |
SLACK_WEBHOOK_URLProduction requiredproperty |
Alert channel: #drop-ops |
BetterStackError uptimetracking |
N/ADisabled |
N/ASentry dev project |
3Sentry monitorsprod (free tier)project |
Production |
uptime
monitoring
| Maps |
No key / free tier |
Paid key |
Paid key |
|
8. Environment Provisioning Process
Staging (Fly.io)
- Infrastructure provisioning:
flyterraform launch — creates new Fly.io app
fly volumes create drop_dataapply --region arn --size 1 — persistent SQLite volume
fly secrets set JWT_SECRET="$(openssl rand -base64 48)" — set secrets
fly secrets set NEXT_PUBLIC_SERVICE_MODE=mockvar-file=envs/{{ENV}}.tfvars
- Secret provisioning:
flybash deployscripts/provision-secrets.sh {{ENV}} — first deployment
- Database provisioning:
curlbash https://drop-staging.fly.dev/api/healthscripts/create-db.sh {{ENV}}
- DNS configuration: Update DNS records per deployment-architecture.md
- TLS certificates: Auto-provisioned via {{CERT_TOOL}}
- Initial deployment: Trigger CI/CD for
{{ENV}} —target
verify- Verification:
healthRun smoke tests against new environment
Estimated time: 10{{PROVISION_TIME}} minutes
Production (AWS App Runner)
Create ECR repository drop-web in eu-west-1
Build and push Docker image to ECR
Create App Runner service from ECR image
Configure environment variables (or link to Secrets Manager)
Configure health check: GET /api/health, 30s interval
Verify service at App Runner URL
Configure BetterStack monitors
Estimated time:Runbook: 30 minutes{{PROVISION_RUNBOOK_LINK}}
9. Environment Teardown Process
Staging teardown:
- Verify no active users or critical processes
- Export any required data / logs
- Remove DNS records
- Revoke TLS certificates
fly appsterraform destroy drop-staging-var-file=envs/{{ENV}}.tfvars — removes Fly.io app and volumes
Clear Fly.ioPurge secrets from secret store
- Archive
stagingenvironment config
Production (never fully torn down — use maintenance mode instead):
Scale App Runner serviceconfiguration to 0 (pause){{ARCHIVE_LOCATION}}
TakeUpdate finalthis RDSdocument snapshotto beforeremove anythe destructiveenvironment action
Any full teardown requires explicit written approval from Alem Bašićentry
10. Parity Policy (Staging ↔ Production Drift)
Goal: Staging should be functionally identical to production at all times.
| Area |
Policy |
| Application version |
Staging is always ahead by ≤ 1 release |
| Infrastructure spec |
Same instance types and topology |
| Database engine & version |
IntentionalMust difference:match SQLite (staging) vs PostgreSQL (production) — tracked for upgradeexactly |
OS & runtime versionversions |
Node.jsMust 22match Alpineexactly |
—
same
| Third-party dependencies |
Same versions (except external service mode) |
| Network topology |
Same (except size) |
| Security controls |
Same security headers and rate limiting |
Feature flags |
Same — all flags match |
External services |
Intentional: mock in staging, live in production |
Drift detection: Manual{{DRIFT_DETECTION}} review before each production deployment
Drift resolution owner: AlemPlatform Bašićteam
Approval
| Role |
Name |
Date |
Signature |
| Author |
Platform Architect (AI) |
2026-02-23 |
|
| Reviewer |
|
|
|
| Approver |
Alem Bašić |
|
|