AWS Infrastructure
bopen is deployed on AWS using nested CloudFormation stacks. All infrastructure is defined as code under infra/cloudformation/.
Network topology
VPC (10.0.0.0/16)
├── Public subnets 10.0.1.0/24, 10.0.2.0/24 ← ALBs, NAT Gateway, Keycloak ECS tasks
├── Private subnets 10.0.10.0/24, 10.0.11.0/24 ← Payment-service ECS tasks, rotation Lambda
└── Database subnets 10.0.20.0/24, 10.0.21.0/24 ← RDS PostgreSQL
Compute — ECS Fargate
All services run as ECS Fargate tasks (serverless containers, no EC2 instances to manage):
| Service | Subnets | Port | Cluster |
|---|---|---|---|
payment-service | Private | 8081 | bopen-dev-cluster |
keycloak | Public | 8080 | bopen-dev-cluster |
IAM task-execution and task roles are defined in ecs.yaml.
Application Load Balancers
Two separate ALBs keep the services isolated:
| ALB | Domain | Routes to |
|---|---|---|
| Shared API ALB | dev.api.bopenbanking.com | payment-service (via API Gateway VPC Link) |
| Keycloak ALB | dev-auth.bopenbanking.com | Keycloak ECS tasks |
Keycloak runs HTTP internally (KC_HTTP_ENABLED=true); in a deployment with a real ACM certificate, the ALB terminates TLS in front of it. The current dev Keycloak ALB deliberately has no ACM certificate attached (AcmCertificateArn="") and serves plain HTTP end to end — this was a source of a confusing 403 HTTPS required error from Keycloak itself (a realm-level setting, unrelated to the actual network path) documented in Keycloak Setup — issue #6. Any environment handling real traffic must attach an ACM certificate and leave Keycloak's sslRequired realm setting at its secure default.
RDS PostgreSQL
| Property | Dev | Prod |
|---|---|---|
| Engine | PostgreSQL 17 | PostgreSQL 17 |
| Instance | db.t3.micro | db.t4g.small |
| Multi-AZ | false | true |
| Deletion protection | false | true |
| Backup retention | 7 days | 7 days |
The RDS instance hosts two isolated schemas:
| Schema | Owner | Purpose |
|---|---|---|
bopen | bopen_app | Application tables — merchants, payments, audit ledger |
keycloak_core | keycloak_user | Keycloak state — sessions, realms, JGROUPSPING cluster table |
Admin credentials are KMS-encrypted in Secrets Manager and auto-rotated by RDS. The keycloak_user password is separately managed (auto-rotated every 30 days via a PostgreSQLMultiUser rotation Lambda — see IAM).
Keycloak on ECS Fargate
See Identity & Access Management for the full Keycloak topology. Infrastructure summary:
- Dedicated internet-facing ALB at
auth.bopenbanking.com - Designed for 2 Fargate tasks spread across AZs with JDBC_PING clustering — the current
bopen-devdeployment runs only 1 task (DesiredCount=1), so there is no active clustering today; see IAM — ECS Fargate deployment for the design-vs-current comparison - JDBC_PING clustering via
keycloak_core.JGROUPSPING(design; inactive with a single task) KeycloakToRdsIngressandRotationLambdaToRdsIngressadd targeted ingress rules on the shared RDS security group — no modification tords.yamlrequired
AWS SES (email)
ses.yaml exists and is designed to send Keycloak email invites (set-password, verify-email):
AWS::SES::EmailIdentitywith DKIM signing onbopenbanking.com- IAM SMTP user with
ses:SendRawEmailpermission scoped tonoreply@bopenbanking.com - SMTP password derived from IAM secret key via
generate-ses-smtp-credentials.sh - Credentials stored in Secrets Manager, injected into payment service at runtime
No SES stack has actually been deployed in bopen-dev — aws secretsmanager list-secrets returns zero SES-related secrets, and aws cloudformation list-stacks shows no SES stack. Keycloak email invites currently fail silently as a result. See IAM — Email invites for the full picture.
DNS records required after deployment:
- 3 DKIM CNAME records (from
SesDkimRecord*stack outputs)
Amplify Hosting
Two separate Amplify apps serve the Next.js frontend:
| App | Domain | Purpose |
|---|---|---|
bopen-dev-web | dev-checkout.bopenbanking.com | Consumer checkout flow. The widget + widget demo exist in the repo but are not yet deployed here — see Checkout Widget — Demo |
bopen-dev-portal | dev-acquirer.bopenbanking.com (CNAME d2fzqf5ucjnale.cloudfront.net) | Acquirer portal |
Both serve the same apps/web static export but are built independently with different NEXT_PUBLIC_KEYCLOAK_URL env vars baked in. The SPA catch-all rule (/<*> → /index.html) ensures Next.js client-side routing works.
| Deploy script | Target |
|---|---|
deploy-web-amplify.sh | Checkout app |
deploy-portal-amplify.sh | Acquirer portal |
GitHub Actions workflows trigger both on apps/web/** changes to main.
Deploying infrastructure
# Full environment (uploads templates, deploys nested stacks)
./infra/cloudformation/scripts/deploy.sh bopen-cfn-templates-... bopen-dev eu-west-2
# Keycloak only
./infra/cloudformation/scripts/deploy-keycloak.sh bopen-cfn-templates-... bopen-dev eu-west-2
# Payment service
./infra/cloudformation/scripts/deploy-payment-service.sh bopen-cfn-templates-... bopen-dev latest eu-west-2
# Acquirer portal
./infra/cloudformation/scripts/deploy-portal-amplify.sh d143ao7wi6kbx main eu-west-2 bopen-dev-portal \
https://dev.api.bopenbanking.com https://dev-auth.bopenbanking.com
Stack summary
| Template | What it provisions |
|---|---|
vpc.yaml | VPC, 6 subnets, NAT Gateway, DB subnet group |
ecs.yaml | ECS cluster, IAM roles, security groups |
ecr.yaml | ECR repositories for container images |
rds.yaml | PostgreSQL RDS, Secrets Manager, auto-rotation |
api-gateway.yaml | HTTP API Gateway, shared ALB, VPC Link, payment-service task |
keycloak.yaml | Keycloak ECS service + dedicated ALB, Secrets Manager, rotation schedule |
ses.yaml | SES domain identity, IAM SMTP user, Secrets Manager |
amplify-web.yaml | Checkout web Amplify app (dev-checkout.bopenbanking.com) |
amplify-portal.yaml | Acquirer portal Amplify app (acquirer.bopenbanking.com) |
amplify-docs.yaml | Documentation site Amplify app (doc.bopenbanking.com) |
logging.yaml | CloudTrail, VPC Flow Logs, S3 archive (dev only) |