Skip to main content
Openfuse

AWS ECS

Deploy Openfuse on AWS ECS with Fargate, ALB hostname-based routing, Secrets Manager, and CDK examples for the API, Keycloak, UI, and Config Importer.

This guide covers deploying Openfuse on AWS ECS (Fargate). For the single-server Docker Compose setup, see Docker Compose.

Prerequisites

  • An AWS account with ECS, ALB, RDS, and Secrets Manager access
  • A managed PostgreSQL 17+ instance (RDS) with two databases: openfuse and keycloak
  • A domain with wildcard DNS records pointing to your ALB
  • ACM certificates for your domain (wildcard recommended)

Architecture

ComponentECS ResourceNotes
APIFargate ServiceStateless, scale freely
KeycloakFargate ServiceSingle task, or clustered for HA
UIFargate ServiceStatic SPA, or use S3 + CloudFront
Config ImporterRunTask (one-off)Execute during deployments

ALB routing

Configure hostname-based routing rules on your Application Load Balancer:

RuleHost patternTarget group
1*.api.<ROOT_DOMAIN>API (port 3000)
2sso.<ROOT_DOMAIN>Keycloak (port 8080)
3DefaultUI (port 80)

TLS terminates at the ALB using ACM certificates.

Secrets Manager

Store all secrets in AWS Secrets Manager and reference them in task definitions:

cdk-example.ts
const apiSecrets = new secretsmanager.Secret(this, 'OpenfuseApiSecrets', {
  secretObjectValue: {
    DATABASE_PASSWORD: SecretValue.unsafePlainText('generate-me'),
    SESSION_SECRET: SecretValue.unsafePlainText('generate-me'),
    KC_STAFF_BACKEND_CLIENT_SECRET: SecretValue.unsafePlainText('generate-me'),
    KC_STAFF_BFF_CLIENT_SECRET: SecretValue.unsafePlainText('generate-me'),
    KC_TENANTS_BACKEND_CLIENT_SECRET: SecretValue.unsafePlainText('generate-me'),
    KC_TENANTS_BFF_CLIENT_SECRET: SecretValue.unsafePlainText('generate-me'),
    KC_TENANTS_SDK_CLIENT_SECRET: SecretValue.unsafePlainText('generate-me'),
  },
})

Reference in task definitions:

secrets: {
  DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(apiSecrets, 'DATABASE_PASSWORD'),
  SESSION_SECRET: ecs.Secret.fromSecretsManager(apiSecrets, 'SESSION_SECRET'),
  KC_STAFF_BACKEND_CLIENT_SECRET: ecs.Secret.fromSecretsManager(apiSecrets, 'KC_STAFF_BACKEND_CLIENT_SECRET'),
  // ...
}

Config Importer

Use RunTask to execute the Config Importer as a one-off Fargate task before updating the API service.

cdk-example.ts
const configImporter = new ecs.FargateTaskDefinition(this, 'ConfigImporter', {
  memoryLimitMiB: 512,
  cpu: 256,
})

configImporter.addContainer('config-importer', {
  image: ecs.ContainerImage.fromRegistry(
    'ghcr.io/openfuseio/openfuse-config-importer:1.0.0'
  ),
  secrets: {
    KEYCLOAK_PASSWORD: ecs.Secret.fromSecretsManager(apiSecrets, 'KC_BOOTSTRAP_ADMIN_PASSWORD'),
    KC_STAFF_BACKEND_CLIENT_SECRET: ecs.Secret.fromSecretsManager(apiSecrets, 'KC_STAFF_BACKEND_CLIENT_SECRET'),
    // ... remaining secrets
  },
  environment: {
    KEYCLOAK_URL: 'http://keycloak.internal:8080',
    IMPORT_VARSUBSTITUTION_ENABLED: 'true',
    IMPORT_VARSUBSTITUTION_UNDEFINEDISTERROR: 'true',
    // ... remaining env vars
  },
  logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'config-importer' }),
})

In CDK, use a Custom Resource to trigger RunTask during deployments. The importer waits up to 120s for Keycloak to be ready before importing.

Run on first deployment and on upgrades that include realm config changes.

API service

cdk-example.ts
const apiTask = new ecs.FargateTaskDefinition(this, 'ApiTask', {
  memoryLimitMiB: 1024,
  cpu: 512,
})

apiTask.addContainer('api', {
  image: ecs.ContainerImage.fromRegistry('ghcr.io/openfuseio/openfuse-api:1.0.0'),
  portMappings: [{ containerPort: 3000 }],
  secrets: {
    DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(apiSecrets, 'DATABASE_PASSWORD'),
    SESSION_SECRET: ecs.Secret.fromSecretsManager(apiSecrets, 'SESSION_SECRET'),
    // ... remaining secrets
  },
  environment: {
    ROOT_DOMAIN: 'openfuse.example.com',
    DATABASE_HOST: rds.instanceEndpoint.hostname,
    DATABASE_SSL: 'true',
    KEYCLOAK_URL: 'http://keycloak.internal:8080',
    KEYCLOAK_EXTERNAL_URL: 'https://sso.openfuse.example.com',
    // ... remaining env vars
  },
  healthCheck: {
    command: ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
  },
  logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'api' }),
})

const apiService = new ecs.FargateService(this, 'ApiService', {
  cluster,
  taskDefinition: apiTask,
  desiredCount: 2,
})

The API is stateless — scale freely. All replicas must share the same SESSION_SECRET.

Static UI alternative

Instead of running the UI container on ECS, serve the SPA from S3 + CloudFront:

  1. Extract static files from the openfuse-ui Docker image
  2. Upload to an S3 bucket
  3. Serve a config.js at the root with runtime configuration
  4. Configure CloudFront with SPA fallback (custom error page → index.html)

This reduces cost and improves global performance via CDN caching.

Next steps

On this page