Environment Configuration
Environment Configuration
Project: {{PROJECT_NAME}}Drop
Version: {{VERSION}}0.1.0
Date: {{DATE}}2026-02-23
Author: {{AUTHOR}}Platform Architect (AI)
Status: Draft | In Review | Approved
Reviewers: {{REVIEWERS}}Alem Bašić (CEO)
Document History
| Version |
Date |
Author |
Changes |
| 0.1 |
{{DATE}}2026-02-23 |
{{AUTHOR}}Platform Architect (AI) |
Initial draft from source code analysis |
1. Environment Overview
| Environment |
Purpose |
URL |
Access |
Managed By |
| Local |
Developer workstation |
localhosthttp://localhost:3000 |
Developer |
Individual |
Dev |
Integration, daily builds |
dev.{{DOMAIN}} |
Team + CI |
Platform team |
| Staging |
Pre-production validation |
drop-staging.{{DOMAIN}}fly.dev (Fly.io Stockholm) |
Team + QA + PMCI |
PlatformAlem teamBašić |
| Production |
Live system |
{{DOMAIN}}https://9ef3szvvsb.eu-west-1.awsapprunner.com (future: getdrop.no) |
OpsAlem Bašić only |
PlatformAlem team |
Preview |
Feature branch review |
{{BRANCH}}.preview.{{DOMAIN}} |
Team + Stakeholders |
CI/CDBašić |
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 |
Log levelPlatform |
Fly.io, region DEBUGarn (Stockholm) |
VerboseApp loggingname: for developmentdrop-staging |
| Database |
SQLite ephemeral volume at dev-db.{{INTERNAL_DOMAIN}}/app/data/drop.db |
SharedNo devautomated DB, refreshed weeklybackup |
CacheNODE_ENV |
dev-redis.{{INTERNAL_DOMAIN}}production |
SharedNo Redis,demo noseed persistencedata |
EmailNEXT_PUBLIC_SERVICE_MODE |
Mailtrap / fake SMTPmock |
EmailsExternal notservices delivered to real recipientsmocked |
PaymentsBANKID_MOCK |
Sandbox / test modetrue |
NoMock realBankID transactions | OIDC
Feature flags |
All enabled |
Developers can test unreleased features |
Debug tools |
Enabled |
Profiler, debug toolbar, etc. |
Rate limiting |
Disabled |
Developer convenienceflow |
Auto-migrationsscaling |
EnabledScale to 0 idle, auto-start on request |
Runs |
on
startup
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) |
|
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.23 StagingProduction Environment
| Parameter |
Value |
Notes |
LogPlatform |
levelAWS App Runner (eu-west-1) |
Service ARN: arn:aws:apprunner:eu-west-1:324480209768:service/drop-web/8e45b0d335304487a1880f4e32d6aeec |
NODE_ENV |
INFOproduction |
SameNo asdemo productionseed, demo credentials disabled |
| Database |
RDS PostgreSQL 16 at staging-drop-db.{{INTERNAL_DOMAIN}}czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432 |
IsolatedDB: stagingdropapp, DB,user: production-scaledropuser |
CacheDATABASE_URL |
staging-redis.{{INTERNAL_DOMAIN}}postgresql://dropuser:<password>@drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432/dropapp |
DedicatedFrom RedisAWS Secrets Manager |
Email |
staging@{{DOMAIN}}JWT_SECRET |
SendsMin to32 internalchars, teststored inboxesin onlyAWS Secrets Manager |
Fatal startup error if missing |
PaymentsNEXT_PUBLIC_SERVICE_MODE |
Sandbox / test modeproduction |
NoReal realexternal transactionsservice calls |
Feature flagsBANKID_MOCK |
MirrorsNot productionset +(false) |
stagedReal featuresBankID 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; preload |
|
Debug toolsCookies |
Disabledsecure: true, httpOnly: true, sameSite: strict |
Must match production behavior |
| Rate limiting |
EnabledPersistent (DB-backed via rate_limits table) |
Same10/min limitsauth, as120/min productionpublic |
Data refreshAuto-scaling |
WeeklyAWS fromApp productionRunner (anonymized)managed |
SeeMinimum 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 year, 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 enabledinstance |
3. Environment Variables Reference
| Variable |
Description |
Required |
Default |
Sensitive |
Environments |
NODE_ENV |
Runtime environment |
Yes |
development |
No |
All |
PORT |
HTTP server port |
YesNo |
3000 |
No |
All |
HOSTNAME |
Server bind address |
No |
0.0.0.0 |
No |
All |
DATABASE_URL |
PostgreSQL connection string |
Yes | No — | (SQLite Yes | if 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 |
All |
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 |
Noabsent) |
— |
Yes |
Staging, Prod |
STRIPE_KEYJWT_SECRET |
PaymentJWT APIsigning key (min 32 chars) |
Yes (if payments)prod) |
—Dev: process.cwd() hash |
Yes |
All |
LOG_LEVELJWT_ALGORITHM |
LoggingHS256 verbosityor RS256 |
No |
infoHS256 |
No |
All |
RATE_LIMIT_WINDOWJWT_EXPIRY |
RateToken limit windowexpiry (ms)web) |
No |
6000024h |
No |
All |
RATE_LIMIT_MAXNEXT_PUBLIC_SERVICE_MODE |
Maxmock requestsor per windowproduction |
No |
100mock |
No |
All |
FEATURE_FLAG_KEYNEXT_PUBLIC_APP_URL |
FeatureApp flagURL SDKfor keyCSRF 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 token |
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
{{SECRET_STORE}}AWS inSecrets stagingManager and(production) productionor Fly.io secrets (staging)
- Never commit sensitive values to source control
- Use
.env.examplelocal with placeholder values for developerlocal onboardingdevelopment (never committed — in .gitignore)
- 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:
Solution:Priority: {{SECRET_TOOL}}DOPPLER_TOKEN set → Doppler | AWS_SECRET_ARN set → AWS Secrets Manager | default → process.env
| Environment |
Secret Store |
Access Method |
| Local |
.envenv.local file (never committed) |
Developer managed |
Dev |
{{DEV_SECRET_STORE}} |
CI/CD service account |
Staging |
{{STG_SECRET_STORE}}Fly.io secrets (fly secrets set) |
IAMAlem roleBašić / service accountCI |
| Production |
{{PROD_SECRET_STORE}}AWS Secrets Manager + App Runner env |
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 |
Database passwordsJWT_SECRET |
90 days |
{{AUTOMATED}}No (manual) |
PlatformAlem teamBašić |
APIDATABASE_URL keys/ (internal)PostgreSQL credentials |
36590 days |
No (manual) |
Alem Bašić |
BANKID_CLIENT_SECRET |
Per BankID policy / on compromise |
No |
Alem Bašić |
SUMSUB_APP_TOKEN |
180 days |
No |
ServiceAlem ownerBašić |
API keys (third-party)SLACK_WEBHOOK_URL |
On compromise only |
No |
DevAlem lead |
JWT signing keys |
365 days |
No |
Platform teamBašić |
| TLS certificates |
60Auto-renewed daysby beforeApp expiryRunner / Fly.io |
{{AUTOMATED}}Yes |
PlatformAWS team/ Fly.io |
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 |
DevLocal Secrets |
Staging Secrets |
Production Secrets |
Developer | Alem Read/Write | Bašić Read |
No access |
DevOps(CEO) |
Read/Write |
Read/Write |
Read/Write |
CI/CDAI Director (build) |
Read |
ReadJohn) |
No access |
Via tooling only |
Via tooling only |
CI/CD (deploy)GitHub Actions) |
No access |
Read (via OIDC) |
Read (deploy role) |
| Application runtime |
Read (scoped)process.env) |
Read (scoped)Fly.io secrets) |
Read (scoped)App Runner env / Secrets Manager) |
5. Feature Flags Per Environment
Tool: {{FF_TOOL}}Environment variables (NEXT_PUBLIC_FF_* baked at build time)
| Flag |
DevLocal |
Staging |
Production |
Notes |
feature-new-checkoutnotifications |
Ontrue |
Ontrue |
Offtrue |
Waiting for QA sign-offEnabled |
feature-dark-modemerchantDashboard |
Ontrue |
Ontrue |
Offtrue |
Rollout planned {{DATE}}Enabled |
kill-switch-paymentsvirtualCards |
Offfalse |
Offfalse |
Offfalse |
EmergencyFuture disable— onlyrequires card issuing partner |
maintenance-modephysicalCards |
Offfalse |
Offfalse |
Offfalse |
EmergencyFuture |
only
cardDetails |
false |
false |
false |
Future |
cardFreeze |
false |
false |
false |
Future |
cardPin |
false |
false |
false |
Future |
spendingLimits |
false |
false |
false |
Future |
6. Database Configuration Per Environment
| Parameter |
Local |
Dev |
Staging |
Production |
| Engine |
SQLite (better-sqlite3) |
SQLite (better-sqlite3) |
PostgreSQL 16 |
| Host |
localhost./drop.db (project root) |
{{DEV_DB}}/app/data/drop.db (Fly volume) |
{{STG_DB}} |
{{PROD_DB}}drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com |
| Port |
5432— |
5432— |
5432 |
5432 |
| Database name |
{{APP}}_dev— |
— |
{{APP}}_dev |
{{APP}}_staging |
{{APP}}_proddropapp |
MaxUsername |
connections— |
— |
10 |
25 |
50 |
{{PROD_CONNS}}dropuser |
| SSL required |
No |
No |
Yes (RDS default) |
| Max connections |
Yes10 (SQLite) |
10 (SQLite) |
~85 (db.t4g.micro default) |
| Connection pool |
No |
No |
YesTBD ({{POOL}}) | PgBouncer Yes ({{POOL}}) |
Read replica |
No |
No |
No |
Yesplanned) |
| Backup |
No |
DailyManual sqlite3 export |
DailyAutomated RDS snapshot, daily at 23:24 UTC, 7-day retention |
| Point-in-Time Recovery |
{{BACKUP_FREQ}}No |
No |
Yes (5-min granularity) |
7. External Service Configuration Per Environment
| Service |
DevLocal |
Staging |
Production |
Notes |
EmailBankID (SMTP)OIDC |
MailtrapMock (BANKID_MOCK=true) |
MailtrapMock |
SendGridLive /(Norwegian SESBankID) |
Mock skips full OIDC flow |
PaymentsSumsub KYC |
StripeMock test(90% approval, 3s delay) |
Stripe testMock |
StripeLive live(api.sumsub.com) |
DifferentOnly APIproduction-ready keysexternal service |
SMSOpen Banking (AISP/PISP) |
TwilioMock test(Swan mock deprecated) |
Twilio testMock |
TwilioTBD liveprovider |
Provider selection pending |
AnalyticsSlack alerts |
DisabledConsole log only (no webhook) |
StagingSLACK_WEBHOOK_URL propertyrequired |
ProductionSLACK_WEBHOOK_URL propertyrequired |
Alert channel: #drop-ops |
ErrorBetterStack trackinguptime |
DisabledN/A |
Sentry dev projectN/A |
Sentry3 prodmonitors project(free tier) |
| Production
uptime
Maps |
No key / free tier |
Paid key |
Paid key |
monitoring |
8. Environment Provisioning Process
Staging (Fly.io)
Infrastructurefly provisioning:launch — creates new Fly.io app
terraformfly applyvolumes create drop_data -var-file=envs/{{ENV}}.tfvars-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=mock
Secretfly provisioning:deploy bash— scripts/provision-secrets.shfirst {{ENV}}deployment
Databasecurl provisioning: bash scripts/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}}https://drop-staging.fly.dev/api/health target — Verification:verify Run smoke tests against new environmenthealth
Estimated time: {{PROVISION_TIME}}10 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
Runbook:Estimated time: {{PROVISION_RUNBOOK_LINK}}30 minutes
9. Environment Teardown Process
Staging teardown:
Verifyfly noapps activedestroy usersdrop-staging or— criticalremoves processesFly.io app and volumes
ExportClear any required data / logs
Remove DNS records
Revoke TLS certificates
terraform destroy -var-file=envs/{{ENV}}.tfvars
PurgeFly.io secrets from secret store
- Archive
environmentstaging configurationconfig
Production (never fully torn down — use maintenance mode instead):
- Scale App Runner service to
{{ARCHIVE_LOCATION}}0 (pause)
UpdateTake thisfinal documentRDS tosnapshot removebefore theany environmentdestructive entryaction
- Any full teardown requires explicit written approval from Alem Bašić
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 |
InfrastructureDatabase specengine |
SameIntentional instancedifference: typesSQLite and(staging) topology | vs
PostgreSQL (production) Database— enginetracked &for version |
Must match exactlyupgrade |
OS & runtime versionsversion |
MustNode.js match22 exactly | Alpine
—
Third-party dependencies |
Same versions (except external service mode) |
Network topology |
Same (except size)same |
| 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: {{DRIFT_DETECTION}}Manual review before each production deployment
Drift resolution owner: PlatformAlem teamBašić
Approval
| Role |
Name |
Date |
Signature |
| Author |
Platform Architect (AI) |
2026-02-23 |
|
| Reviewer |
|
|
|
| Approver |
Alem Bašić |
|
|