Skip to main content

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):

ServiceSubnetsPortCluster
payment-servicePrivate8081bopen-dev-cluster
keycloakPublic8080bopen-dev-cluster

IAM task-execution and task roles are defined in ecs.yaml.

Application Load Balancers

Two separate ALBs keep the services isolated:

ALBDomainRoutes to
Shared API ALBdev.api.bopenbanking.compayment-service (via API Gateway VPC Link)
Keycloak ALBdev-auth.bopenbanking.comKeycloak 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

PropertyDevProd
EnginePostgreSQL 17PostgreSQL 17
Instancedb.t3.microdb.t4g.small
Multi-AZfalsetrue
Deletion protectionfalsetrue
Backup retention7 days7 days

The RDS instance hosts two isolated schemas:

SchemaOwnerPurpose
bopenbopen_appApplication tables — merchants, payments, audit ledger
keycloak_corekeycloak_userKeycloak 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-dev deployment 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)
  • KeycloakToRdsIngress and RotationLambdaToRdsIngress add targeted ingress rules on the shared RDS security group — no modification to rds.yaml required

AWS SES (email)

ses.yaml exists and is designed to send Keycloak email invites (set-password, verify-email):

  • AWS::SES::EmailIdentity with DKIM signing on bopenbanking.com
  • IAM SMTP user with ses:SendRawEmail permission scoped to noreply@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
Not currently deployed

No SES stack has actually been deployed in bopen-devaws 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:

AppDomainPurpose
bopen-dev-webdev-checkout.bopenbanking.comConsumer checkout flow. The widget + widget demo exist in the repo but are not yet deployed here — see Checkout Widget — Demo
bopen-dev-portaldev-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 scriptTarget
deploy-web-amplify.shCheckout app
deploy-portal-amplify.shAcquirer 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

TemplateWhat it provisions
vpc.yamlVPC, 6 subnets, NAT Gateway, DB subnet group
ecs.yamlECS cluster, IAM roles, security groups
ecr.yamlECR repositories for container images
rds.yamlPostgreSQL RDS, Secrets Manager, auto-rotation
api-gateway.yamlHTTP API Gateway, shared ALB, VPC Link, payment-service task
keycloak.yamlKeycloak ECS service + dedicated ALB, Secrets Manager, rotation schedule
ses.yamlSES domain identity, IAM SMTP user, Secrets Manager
amplify-web.yamlCheckout web Amplify app (dev-checkout.bopenbanking.com)
amplify-portal.yamlAcquirer portal Amplify app (acquirer.bopenbanking.com)
amplify-docs.yamlDocumentation site Amplify app (doc.bopenbanking.com)
logging.yamlCloudTrail, VPC Flow Logs, S3 archive (dev only)