Infrastructure
Environments
| Environment | Web | API | Purpose |
|---|---|---|---|
| Production | Not yet built | Not yet built | Production service |
| dev | https://survey-design-web.heineken.dev.4digit.ai | https://survey-design-api.heineken.dev.4digit.ai | Verification and integration tests. Shared by the team |
| Local | http://localhost:3000 | http://localhost:8787 | Local development |
dev is a shared environment
dev holds other members' real data. The integration tests create a new survey every run, so clear them out with bun run cleanup in integration-tests when they pile up. The dev API can be called with just the x-user-email and x-user-name headers.
Architecture
graph TD
User[Survey designer]
Ext[Chrome extension]
Google[Google OAuth]
AppRunner[AWS App Runner<br/>frontend Next.js]
APILambda[AWS Lambda<br/>backend API]
MigLambda[AWS Lambda<br/>migration]
DB[(PostgreSQL)]
ECR[Amazon ECR]
GHA[GitHub Actions]
CS[(Creative Survey)]
User --> AppRunner
Google -.-> AppRunner
AppRunner --> APILambda
Ext --> APILambda
Ext --> CS
APILambda --> DB
MigLambda --> DB
GHA --> ECR
ECR --> AppRunner
ECR --> APILambda
ECR --> MigLambda
Services
| Service | Purpose |
|---|---|
| AWS App Runner | Hosts the frontend (Next.js), started from a container image |
| AWS Lambda | Hosts the backend API as a container image (based on public.ecr.aws/lambda/nodejs:22) |
| AWS Lambda (migration) | Runs the Drizzle migrations, as a function separate from the API |
| Amazon ECR | Registry for the three container images (web / backend / migration) |
| PostgreSQL | The main database |
| GitHub Actions | Builds, pushes to ECR, and deploys |
The region is ap-northeast-1 (Tokyo).
Components and Repositories
| Component | Runtime | ECR repository (dev) | Lambda / service name (dev) |
|---|---|---|---|
| frontend (web) | App Runner | dev-heineken-survey-design-web |
dev-heineken-survey-design-web |
| backend API | Lambda | dev-heineken-survey-design-backend |
dev-heineken-survey-design-backend |
| Migration | Lambda | dev-heineken-survey-design-backend-migration |
dev-heineken-survey-design-backend-migration |
Container Images
backend API (Dockerfile)
| Stage | Contents |
|---|---|
build (oven/bun) |
Installs dependencies and bundles src/lambda.ts into CommonJS with bun build |
runtime (public.ecr.aws/lambda/nodejs:22) |
Places the bundle, node_modules, drizzle/, and openapi.json, and starts lambda.handler |
Only the postgres package is excluded from the bundle (--external postgres) and loaded from node_modules.
Migration (Dockerfile.migration)
Almost identical to the API image, but the entry point is migrate-handler.handler. Both postgres and drizzle-orm are excluded from the bundle.
frontend (Dockerfile)
A three-stage build (deps / build / release) based on oven/bun, running next start on port 3000.
NEXT_PUBLIC_API_URL is baked in as a build argument, so changing the target API requires rebuilding the image.
Deployment Flow
| Branch | Deploys to | Timing |
|---|---|---|
main (backend) |
The dev API Lambda | Automatic (on push) |
main (backend, when drizzle/** changes) |
The dev migration Lambda | Automatic (on push) or manual |
main (frontend) |
The dev App Runner service | Automatic (on push) |
backend: Dev Deploy
graph LR
Push[Push to main] --> Checkout[Checkout]
Checkout --> Creds[Configure AWS credentials]
Creds --> Login[Log in to ECR]
Login --> Build[docker build]
Build --> PushImg[Push to ECR<br/>tags: commit SHA and latest]
PushImg --> Update[aws lambda update-function-code]
backend: Dev Migration
Triggered by a push to main that touches drizzle/**, or manually (workflow_dispatch).
- Build the migration image and push it to ECR
- Check whether the migration Lambda exists (skip the rest if it does not)
- Update the Lambda's image and wait for the update to finish
- Invoke the Lambda and print the logs
Why migrations are separated
The API Lambda starts per request, so running migrations at startup would run them many times over. They live in an independent Lambda that is invoked explicitly exactly once.
frontend: Dev Deploy
- Build the image with
NEXT_PUBLIC_API_URLas a build argument - Push it to ECR (tags: commit SHA and
latest) - Start the App Runner deployment with
aws apprunner start-deployment
Required GitHub Secrets
| Secret | Purpose |
|---|---|
AWS_ACCESS_KEY_ID |
Operating ECR / Lambda / App Runner |
AWS_SECRET_ACCESS_KEY |
Same as above |
Environment Variables
backend
| Name | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string. Certificate verification is disabled when it contains sslmode=no-verify |
SURVEY_ADMIN_EMAILS |
Administrator email addresses (comma separated) |
SURVEY_IMPORT_API_KEY |
API key for the import API. Key authentication is disabled when unset |
frontend
| Name | Purpose |
|---|---|
NEXTAUTH_URL |
Base URL for next-auth |
NEXTAUTH_SECRET |
Session encryption key |
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
NEXT_PUBLIC_API_URL |
Base URL of the backend API (baked in at build time) |
Authentication and Access Paths
| Path | Authentication |
|---|---|
| User โ frontend | Google login via next-auth. /surveys/* and /library/* protected by middleware |
| frontend โ backend | User information in the x-user-email / x-user-name / x-user-image headers |
| Chrome extension โ backend (import) | API key authentication via x-api-key (compared against SURVEY_IMPORT_API_KEY) |
| Chrome extension โ CS | Uses the existing session of the CS editing screen |
The backend allows CORS from any origin and accepts the Content-Type / x-user-email / x-user-name / x-user-image / x-api-key headers.
Known Issues
Confirmed problems that need addressing before a production rollout.
| Issue | Contents |
|---|---|
| Impersonation | The backend trusts the x-user-email header, so forging it allows acting as any user |
| Permission bypass | The snapshot direct-read fallback in importSection skips the permission check (survey-store.ts) |
| Fully open CORS | origin: "*" allows every origin |
| Default users auto-created | DEFAULT_ACTORS (@survey.local) are created automatically even in production |
| Missing Makefile | No Makefile exists for the make commands in the READMEs |