Skip to main content

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

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

Sharedflow to 0 idle, auto-start
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
CacheNODE_ENVdev-redis.{{INTERNAL_DOMAIN}} production 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
Feature flagsAll enabledDevelopers can test unreleased features
Debug toolsEnabledProfiler, debug toolbar, etc.
Rate limitingDisabledDeveloper convenience
Auto-scalingmigrations ScaleEnabled Runs on request
SEED_DEMOOptional — set to seed test data
StoragePersistent Fly.io volume drop_data at /app/data
SecretsFly.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

Dedicated +
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 productionNo 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
CacheDATABASE_URLstaging-redis.{{INTERNAL_DOMAIN}} postgresql://dropuser:<password>@drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432/dropapp From AWS Secrets ManagerRedis
EmailJWT_SECRETstaging@{{DOMAIN}} MinSends 32to chars,internal storedtest ininboxes AWS Secrets ManagerFatal 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_URLFrom AWS Secrets ManagerOperational alerts to #drop-ops
Security headersFull 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

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 instanceyear, 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 enabled

3. Environment Variables Reference

(SQLiteifabsent)
Variable Description Required Default Sensitive Environments
NODE_ENV Runtime environment Yes development No All
PORT HTTP server port NoYes 3000NoAll
HOSTNAMEServer bind addressNo0.0.0.0 No All
DATABASE_URL PostgreSQL connection string YesYesAll
REDIS_URLRedis connection stringYesredis://localhost:6379YesAll
JWT_SECRETJWT signing keyYesYesAll
JWT_EXPIRYToken expiry durationYes1hNo All
SMTP_HOSTSMTP server hostnameYesNoAll
SMTP_USERSMTP usernameYesYesAll
SMTP_PASSSMTP passwordYesYesAll
S3_BUCKETObject storage bucket nameYesNoAll
AWS_REGIONCloud regionYeseu-west-1NoAll
SENTRY_DSNError tracking DSNNo 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 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 tokenkey 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 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_URLOn compromise onlyNoAlem 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

Bašić(CEO)
Role LocalDev Secrets Staging Secrets Production Secrets
AlemDeveloper Read/Write ReadNo access
DevOps Read/Write Read/Write Read/Write
AI DirectorCI/CD (John)build)ReadRead No accessVia tooling onlyVia 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}}

Emergency
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
cardDetailsfalsefalsefalseFuture
cardFreezefalsefalsefalseFuture
cardPinfalsefalsefalseFuture
spendingLimitsfalsefalsefalseFutureonly

6. Database Configuration Per Environment

Max planned)
Parameter LocalDev Staging Production
EngineSQLite (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 54325432
Database name {{APP}}_dev dropapp{{APP}}_dev{{APP}}_staging{{APP}}_prod
Username connections dropuser102550{{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}}) Yes ({{POOL}})
Read replicaNoNoNoYes
Backup No Manual sqlite3 exportDaily Automated RDS snapshot, daily at 23:24 UTC, 7-day retention
Point-in-Time RecoveryDaily NoNoYes (5-min granularity){{BACKUP_FREQ}}

7. External Service Configuration Per Environment

uptimemonitoring
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
MapsNo key / free tierPaid keyPaid key

8. Environment Provisioning Process

Staging (Fly.io)

  1. Infrastructure provisioning: flyterraform launch — creates new Fly.io app
  2. fly volumes create drop_dataapply --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=mockvar-file=envs/{{ENV}}.tfvars
  5. Secret provisioning: flybash deployscripts/provision-secrets.sh {{ENV}} — first deployment
  6. Database provisioning: curlbash https://drop-staging.fly.dev/api/healthscripts/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}} target
  10. verify
  11. Verification: healthRun smoke tests against new environment

Estimated time: 10{{PROVISION_TIME}} 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

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


9. Environment Teardown Process

Staging teardown:

  1. Verify no active users or critical processes
  2. Export any required data / logs
  3. Remove DNS records
  4. Revoke TLS certificates
  5. fly appsterraform destroy drop-staging-var-file=envs/{{ENV}}.tfvars — removes Fly.io app and volumes
  6. Clear Fly.ioPurge secrets from secret store
  7. Archive stagingenvironment config

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

  1. Scale App Runner serviceconfiguration to 0 (pause){{ARCHIVE_LOCATION}}
  2. TakeUpdate finalthis RDSdocument snapshotto beforeremove anythe destructiveenvironment action
  3. 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.

same
Area Policy
Application version Staging is always ahead by ≤ 1 release
Infrastructure specSame 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
Third-party dependenciesSame versions (except external service mode)
Network topologySame (except size)
Security controls Same security headers and rate limiting
Feature flagsSame — all flags match
External servicesIntentional: 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ć