Environment Configuration
Environment Configuration
Project: Drop
Version: 0.1.0
Date: 2026-02-23
Author: Platform Architect (AI)
Status: In Review
Reviewers: Alem Bašić (CEO)
Document History
| Version |
Date |
Author |
Changes |
| 0.1 |
2026-02-23 |
Platform Architect (AI) |
Initial draft from source code analysis |
1. Environment Overview
| Environment |
Purpose |
URL |
Access |
Managed By |
| Local |
Developer workstation |
http://localhost:3000 |
Developer |
Individual |
| Staging |
Pre-production validation |
drop-staging.fly.dev (Fly.io Stockholm) |
Team + CI |
Alem Bašić |
| Production |
Live system |
https://9ef3szvvsb.eu-west-1.awsapprunner.com (future: getdrop.no) |
Alem Bašić only |
Alem Baš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 |
| Platform |
Fly.io, region arn (Stockholm) |
App name: drop-staging |
| Database |
SQLite ephemeral volume at /app/data/drop.db |
No automated backup |
NODE_ENV |
production |
No demo seed data |
NEXT_PUBLIC_SERVICE_MODE |
mock |
External services mocked |
BANKID_MOCK |
true |
Mock BankID OIDC flow |
| Auto-scaling |
Scale to 0 idle, auto-start 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) |
|
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.3 Production Environment
| Parameter |
Value |
Notes |
| Platform |
AWS App Runner (eu-west-1) |
Service ARN: arn:aws:apprunner:eu-west-1:324480209768:service/drop-web/8e45b0d335304487a1880f4e32d6aeec |
NODE_ENV |
production |
No demo seed, demo credentials disabled |
| Database |
RDS PostgreSQL 16 at drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432 |
DB: dropapp, user: dropuser |
DATABASE_URL |
postgresql://dropuser:<password>@drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432/dropapp |
From AWS Secrets Manager |
JWT_SECRET |
Min 32 chars, stored in AWS Secrets Manager |
Fatal startup error if missing |
NEXT_PUBLIC_SERVICE_MODE |
production |
Real external service calls |
BANKID_MOCK |
Not set (false) |
Real 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; preload |
|
| Cookies |
secure: true, httpOnly: true, sameSite: strict |
|
| Rate limiting |
Persistent (DB-backed via rate_limits table) |
10/min auth, 120/min public |
| Auto-scaling |
AWS App Runner managed |
Minimum 1 instance |
3. Environment Variables Reference
| Variable |
Description |
Required |
Default |
Sensitive |
Environments |
NODE_ENV |
Runtime environment |
Yes |
development |
No |
All |
PORT |
HTTP server port |
No |
3000 |
No |
All |
HOSTNAME |
Server bind address |
No |
0.0.0.0 |
No |
All |
DATABASE_URL |
PostgreSQL connection string |
No (SQLite if absent) |
— |
Yes |
Staging, Prod |
JWT_SECRET |
JWT signing key (min 32 chars) |
Yes (prod) |
Dev: process.cwd() hash |
Yes |
All |
JWT_ALGORITHM |
HS256 or RS256 |
No |
HS256 |
No |
All |
JWT_EXPIRY |
Token expiry (web) |
No |
24h |
No |
All |
NEXT_PUBLIC_SERVICE_MODE |
mock or production |
No |
mock |
No |
All |
NEXT_PUBLIC_APP_URL |
App URL for 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 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 AWS Secrets Manager (production) or Fly.io secrets (staging)
- Never commit sensitive values to source control
- Use
.env.local for local development (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:
Priority: DOPPLER_TOKEN set → Doppler | AWS_SECRET_ARN set → AWS Secrets Manager | default → process.env
| Environment |
Secret Store |
Access Method |
| Local |
.env.local (never committed) |
Developer managed |
| Staging |
Fly.io secrets (fly secrets set) |
Alem Bašić / CI |
| Production |
AWS Secrets Manager + App Runner env |
App Runner IAM role |
Cache TTL: 5 minutes (in-memory, resets on restart)
4.2 Secret Rotation Schedule
| Secret Type |
Rotation Schedule |
Automated |
Owner |
JWT_SECRET |
90 days |
No (manual) |
Alem Bašić |
DATABASE_URL / PostgreSQL credentials |
90 days |
No (manual) |
Alem Bašić |
BANKID_CLIENT_SECRET |
Per BankID policy / on compromise |
No |
Alem Bašić |
SUMSUB_APP_TOKEN |
180 days |
No |
Alem Bašić |
SLACK_WEBHOOK_URL |
On compromise only |
No |
Alem Bašić |
| TLS certificates |
Auto-renewed by App Runner / Fly.io |
Yes |
AWS / 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 |
Local Secrets |
Staging Secrets |
Production Secrets |
| Alem Bašić (CEO) |
Read/Write |
Read/Write |
Read/Write |
| AI Director (John) |
No access |
Via tooling only |
Via tooling only |
| CI/CD (GitHub Actions) |
No access |
Read (via OIDC) |
Read (deploy role) |
| Application runtime |
Read (process.env) |
Read (Fly.io secrets) |
Read (App Runner env / Secrets Manager) |
5. Feature Flags Per Environment
Tool: Environment variables (NEXT_PUBLIC_FF_* baked at build time)
| Flag |
Local |
Staging |
Production |
Notes |
notifications |
true |
true |
true |
Enabled |
merchantDashboard |
true |
true |
true |
Enabled |
virtualCards |
false |
false |
false |
Future — requires card issuing partner |
physicalCards |
false |
false |
false |
Future |
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 |
Staging |
Production |
| Engine |
SQLite (better-sqlite3) |
SQLite (better-sqlite3) |
PostgreSQL 16 |
| Host |
./drop.db (project root) |
/app/data/drop.db (Fly volume) |
drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com |
| Port |
— |
— |
5432 |
| Database name |
— |
— |
dropapp |
| Username |
— |
— |
dropuser |
| SSL required |
No |
No |
Yes (RDS default) |
| Max connections |
10 (SQLite) |
10 (SQLite) |
~85 (db.t4g.micro default) |
| Connection pool |
No |
No |
TBD (PgBouncer planned) |
| Backup |
No |
Manual sqlite3 export |
Automated RDS snapshot, daily at 23:24 UTC, 7-day retention |
| Point-in-Time Recovery |
No |
No |
Yes (5-min granularity) |
7. External Service Configuration Per Environment
| Service |
Local |
Staging |
Production |
Notes |
| BankID OIDC |
Mock (BANKID_MOCK=true) |
Mock |
Live (Norwegian BankID) |
Mock skips full OIDC flow |
| Sumsub KYC |
Mock (90% approval, 3s delay) |
Mock |
Live (api.sumsub.com) |
Only production-ready external service |
| Open Banking (AISP/PISP) |
Mock (Swan mock deprecated) |
Mock |
TBD provider |
Provider selection pending |
| Slack alerts |
Console log only (no webhook) |
SLACK_WEBHOOK_URL required |
SLACK_WEBHOOK_URL required |
Alert channel: #drop-ops |
| BetterStack uptime |
N/A |
N/A |
3 monitors (free tier) |
Production uptime monitoring |
8. Environment Provisioning Process
Staging (Fly.io)
fly launch — creates new Fly.io app
fly volumes create drop_data --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
fly deploy — first deployment
curl https://drop-staging.fly.dev/api/health — verify health
Estimated 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
Estimated time: 30 minutes
9. Environment Teardown Process
Staging teardown:
fly apps destroy drop-staging — removes Fly.io app and volumes
- Clear Fly.io secrets
- Archive staging config
Production (never fully torn down — use maintenance mode instead):
- Scale App Runner service to 0 (pause)
- Take final RDS snapshot before any destructive action
- Any full teardown requires explicit written approval from Alem Bašić
10. Parity Policy (Staging ↔ Production Drift)
| Area |
Policy |
| Application version |
Staging is always ahead by ≤ 1 release |
| Database engine |
Intentional difference: SQLite (staging) vs PostgreSQL (production) — tracked for upgrade |
| OS & runtime version |
Node.js 22 Alpine — 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: Manual review before each production deployment
Drift resolution owner: Alem Bašić
Approval
| Role |
Name |
Date |
Signature |
| Author |
Platform Architect (AI) |
2026-02-23 |
|
| Reviewer |
|
|
|
| Approver |
Alem Bašić |
|
|