Skip to main content

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
DevIntegration, daily buildsdev.{{DOMAIN}}Team + CIPlatform 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
PreviewFeature branch review{{BRANCH}}.preview.{{DOMAIN}}Team + StakeholdersCI/CDBašić

2. Per-Environment Configuration

2.1 Development Environment

ParameterValueNotes
NODE_ENVdevelopmentAuto-seeds demo user ([email protected] / demo1234)
DatabaseSQLite at ./drop.db (project root)Auto-detected when no DATABASE_URL set
AuthEmail/password login (legacy, deprecated in prod) OR BANKID_MOCK=trueBankID mock skips OIDC flow
Service modeNEXT_PUBLIC_SERVICE_MODE=mockNo real external API calls
Rate limitingEnabled (in-memory fallback when no DB table)Lower limits in dev are OK
JWT_SECRETDev fallback (hash of process.cwd())Fatal error if missing in production
Demo userAuto-seededLogin at http://localhost:3000 with demo credentials
Security headersCSP with unsafe-eval + unsafe-inlineRequired for Next.js HMR
Cookiessecure: falseNo HTTPS in local dev

2.2 Staging Environment

OIDC onstartup
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
Feature flagsAll enabledDevelopers can test unreleased features
Debug toolsEnabledProfiler, debug toolbar, etc.
Rate limitingDisabledDeveloper convenienceflow
Auto-migrationsscaling EnabledScale to 0 idle, auto-start on request Runs
SEED_DEMOOptional — set to seed test data
StoragePersistent Fly.io volume drop_data at /app/data
SecretsFly.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

level staged
Parameter Value Notes
LogPlatform AWS 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
Emailstaging@{{DOMAIN}}JWT_SECRET SendsMin to32 internalchars, teststored inboxesin onlyAWS Secrets ManagerFatal startup error if missing
PaymentsNEXT_PUBLIC_SERVICE_MODE Sandbox / test modeproduction NoReal realexternal transactionsservice calls
Feature flagsBANKID_MOCK MirrorsNot productionset +(false) Real featuresBankID OIDC
SLACK_WEBHOOK_URLFrom AWS Secrets ManagerOperational alerts to #drop-ops
Security headersFull 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

ParameterValueNotes
Log levelWARNErrors 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.
PaymentsLive modeReal transactions
Feature flagsConservative — tested features onlyNew features behind flags
Debug toolsDisabledSecurity requirement
Rate limitingEnabledSee rate limit table
HSTSEnabled (1 year, includeSubDomains)
CSPStrictSee 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

ParameterValue
Log levelDEBUG
EmailFake SMTP / preview inbox
PaymentsSandbox
Feature flagsBranch-specific flags enabledinstance

3. Environment Variables Reference

No(SQLiteif
Variable Description Required Default Sensitive Environments
NODE_ENV Runtime environment Yes development No All
PORT HTTP server port YesNo 3000NoAll
HOSTNAMEServer bind addressNo0.0.0.0 No All
DATABASE_URL PostgreSQL connection string Yes Yes All
REDIS_URLRedis connection stringYesredis://localhost:6379YesAll
JWT_SECRETJWT signing keyYesYesAll
JWT_EXPIRYToken expiry durationYes1hNoAll
SMTP_HOSTSMTP server hostnameYesNoAll
SMTP_USERSMTP usernameYesYesAll
SMTP_PASSSMTP passwordYesYesAll
S3_BUCKETObject storage bucket nameYesNoAll
AWS_REGIONCloud regionYeseu-west-1NoAll
SENTRY_DSNError tracking DSNNoabsent) 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 checkNoNoStaging, Prod
SLACK_WEBHOOK_URLSlack incoming webhook for alertsYes (prod)YesStaging, Prod
BANKID_CLIENT_IDBankID OIDC client IDYes (prod)NoStaging, Prod
BANKID_CLIENT_SECRETBankID OIDC client secretYes (prod)YesStaging, Prod
BANKID_CALLBACK_URLBankID web callback URLYes (prod)NoStaging, Prod
BANKID_CALLBACK_URL_MOBILEBankID mobile deep linkYes (mobile)NoStaging, Prod
BANKID_MOCKSkip real BankID OIDC (true)NofalseNoLocal, Staging
SUMSUB_API_URLSumsub KYC API endpointNohttps://api.sumsub.comNoAll
SUMSUB_APP_TOKENSumsub API tokenYes (prod)YesStaging, Prod
SUMSUB_SECRET_KEYSumsub webhook secretYes (prod)YesStaging, Prod
DOPPLER_TOKENDoppler secrets provider token No Yes Optional
AWS_SECRET_ARNAWS Secrets Manager ARNNoYesProd (optional)
SEED_DEMOSeed demo data in stagingNoNoStaging
NEXT_PUBLIC_FF_NOTIFICATIONSEnable notifications featureNotrueNoAll
NEXT_PUBLIC_FF_MERCHANT_DASHBOARDEnable merchant dashboardNotrueNoAll
NEXT_PUBLIC_FF_VIRTUAL_CARDSEnable virtual cards (future)NofalseNoAll
NEXT_PUBLIC_FF_PHYSICAL_CARDSEnable physical cards (future)NofalseNoAll
NEXT_PUBLIC_FF_CARD_DETAILSEnable card details (future)NofalseNoAll
NEXT_PUBLIC_FF_CARD_FREEZEEnable card freeze (future)NofalseNoAll
NEXT_PUBLIC_FF_CARD_PINEnable card PIN (future)NofalseNoAll
NEXT_PUBLIC_FF_SPENDING_LIMITSEnable spending limits (future)NofalseNoAll

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 daysNo (manual)Alem Bašić
BANKID_CLIENT_SECRETPer BankID policy / on compromiseNoAlem Bašić
SUMSUB_APP_TOKEN180 days No ServiceAlem ownerBašić
API keys (third-party)SLACK_WEBHOOK_URL On compromise only No DevAlem lead
JWT signing keys365 daysNoPlatform 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

AlemBašić
Role DevLocal Secrets Staging Secrets Production Secrets
Developer Read/Write ReadNo access
DevOps(CEO) Read/Write Read/Write Read/Write
CI/CDAI Director (build)ReadReadJohn) No accessVia tooling onlyVia 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)

only
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
cardDetailsfalsefalsefalseFuture
cardFreezefalsefalsefalseFuture
cardPinfalsefalsefalseFuture
spendingLimitsfalsefalsefalseFuture

6. Database Configuration Per Environment

connections PgBouncer
Parameter LocalDev Staging Production
EngineSQLite (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 54325432
Database name {{APP}}_dev {{APP}}_dev{{APP}}_staging{{APP}}_proddropapp
MaxUsername 102550{{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}}) Yes ({{POOL}})
Read replicaNoNoNoYesplanned)
Backup No DailyManual sqlite3 export DailyAutomated RDS snapshot, daily at 23:24 UTC, 7-day retention
Point-in-Time Recovery {{BACKUP_FREQ}}NoNoYes (5-min granularity)

7. External Service Configuration Per Environment

Productionuptime
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)
MapsNo key / free tierPaid keyPaid keymonitoring

8. Environment Provisioning Process

Staging (Fly.io)

  1. Infrastructurefly provisioning:launch — creates new Fly.io app
  2. terraformfly applyvolumes create drop_data -var-file=envs/{{ENV}}.tfvars-region arn --size 1 — persistent SQLite volume
  3. fly secrets set JWT_SECRET="$(openssl rand -base64 48)" — set secrets
  4. fly secrets set NEXT_PUBLIC_SERVICE_MODE=mock
  5. Secretfly provisioning:deploy bash scripts/provision-secrets.shfirst {{ENV}}deployment
  6. Databasecurl provisioning: bash scripts/create-db.sh {{ENV}}
  7. DNS configuration: Update DNS records per deployment-architecture.md
  8. TLS certificates: Auto-provisioned via {{CERT_TOOL}}
  9. Initial deployment: Trigger CI/CD for {{ENV}}https://drop-staging.fly.dev/api/health target
  10. Verification:verify Run smoke tests against new environmenthealth

Estimated time: {{PROVISION_TIME}}10 minutes

Production (AWS App Runner)

  1. Create ECR repository drop-web in eu-west-1
  2. Build and push Docker image to ECR
  3. Create App Runner service from ECR image
  4. Configure environment variables (or link to Secrets Manager)
  5. Configure health check: GET /api/health, 30s interval
  6. Verify service at App Runner URL
  7. Configure BetterStack monitors

Runbook:Estimated time: {{PROVISION_RUNBOOK_LINK}}30 minutes


9. Environment Teardown Process

Staging teardown:

  1. Verifyfly noapps activedestroy usersdrop-staging or criticalremoves processesFly.io app and volumes
  2. ExportClear any required data / logs
  3. Remove DNS records
  4. Revoke TLS certificates
  5. terraform destroy -var-file=envs/{{ENV}}.tfvars
  6. PurgeFly.io secrets from secret store
  7. Archive environmentstaging configurationconfig

Production (never fully torn down — use maintenance mode instead):

  1. Scale App Runner service to {{ARCHIVE_LOCATION}}0 (pause)
  2. UpdateTake thisfinal documentRDS tosnapshot removebefore theany environmentdestructive entryaction
  3. 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.

vsPostgreSQL(production) Alpine
Area Policy
Application version Staging is always ahead by ≤ 1 release
InfrastructureDatabase specengine SameIntentional instancedifference: typesSQLite and(staging) topology
Database enginetracked &for versionMust match exactlyupgrade
OS & runtime versionsversion MustNode.js match22 exactly
Third-party dependenciesSame versions (except external service mode)
Network topologySame (except size)same
Security controls Same security headers and rate limiting
Feature flagsSame — all flags match
External servicesIntentional: 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ć