Repository: aws-samples/cdk-keycloak (language: python)

Issue:
Support for Quarkus based KeyCloak versions
Hi, 

It looks like currently only older KeyCloak versions (<17) are supported (WildFly based).

```
const KEYCLOAK_DOCKER_IMAGE_URI_MAP: dockerImageMap = {
  'aws': 'jboss/keycloak:',
  'aws-cn': '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/dockerhub/jboss/keycloak:',
};
```

Any plans to support newer versions (>=17), based on Quarkus?



Discussion / hints:

Fix patch:
diff --git a/API.md b/API.md
index d496753..70c3756 100644
--- a/API.md
+++ b/API.md
@@ -49,7 +49,9 @@ new ContainerService(scope: Construct, id: string, props: ContainerServiceProps)
   * **autoScaleTask** (<code>[AutoScaleTask](#cdk-keycloak-autoscaletask)</code>)  Autoscaling for the ECS Service. __*Default*__: no ecs service autoscaling
   * **bastion** (<code>boolean</code>)  Whether to create the bastion host. __*Default*__: false
   * **circuitBreaker** (<code>boolean</code>)  Whether to enable the ECS service deployment circuit breaker. __*Default*__: false
+  * **containerImage** (<code>[aws_ecs.ContainerImage](#aws-cdk-lib-aws-ecs-containerimage)</code>)  Overrides the default image. __*Default*__: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
   * **env** (<code>Map<string, string></code>)  The environment variables to pass to the keycloak container. __*Optional*__
+  * **hostname** (<code>string</code>)  The hostname to use for the keycloak server. __*Optional*__
   * **internetFacing** (<code>boolean</code>)  Whether to put the put the load balancer in the public or private subnets. __*Default*__: true
   * **nodeCount** (<code>number</code>)  Number of keycloak node in the cluster. __*Default*__: 1
   * **privateSubnets** (<code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code>)  VPC subnets for keycloak service. __*Optional*__
@@ -142,12 +144,14 @@ new KeyCloak(scope: Construct, id: string, props: KeyCloakProps)
   * **backupRetention** (<code>[Duration](#aws-cdk-lib-duration)</code>)  database backup retension. __*Default*__: 7 days
   * **bastion** (<code>boolean</code>)  Create a bastion host for debugging or trouble-shooting. __*Default*__: false
   * **clusterEngine** (<code>[aws_rds.IClusterEngine](#aws-cdk-lib-aws-rds-iclusterengine)</code>)  The database cluster engine. __*Default*__: rds.AuroraMysqlEngineVersion.VER_2_09_1
+  * **containerImage** (<code>[aws_ecs.ContainerImage](#aws-cdk-lib-aws-ecs-containerimage)</code>)  Overrides the default image. __*Default*__: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
   * **databaseInstanceType** (<code>[aws_ec2.InstanceType](#aws-cdk-lib-aws-ec2-instancetype)</code>)  Database instance type. __*Default*__: r5.large
   * **databaseMaxCapacity** (<code>number</code>)  The maximum number of Aurora Serverless V2 capacity units. __*Default*__: 10
   * **databaseMinCapacity** (<code>number</code>)  The minimum number of Aurora Serverless V2 capacity units. __*Default*__: 0.5
   * **databaseRemovalPolicy** (<code>[RemovalPolicy](#aws-cdk-lib-removalpolicy)</code>)  Controls what happens to the database if it stops being managed by CloudFormation. __*Default*__: RemovalPolicy.RETAIN
   * **databaseSubnets** (<code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code>)  VPC subnets for database. __*Default*__: VPC isolated subnets
   * **env** (<code>Map<string, string></code>)  The environment variables to pass to the keycloak container. __*Optional*__
+  * **hostname** (<code>string</code>)  The hostname to use for the keycloak server. __*Optional*__
   * **instanceEngine** (<code>[aws_rds.IInstanceEngine](#aws-cdk-lib-aws-rds-iinstanceengine)</code>)  The database instance engine. __*Default*__: MySQL 8.0.21
   * **internetFacing** (<code>boolean</code>)  Whether to put the load balancer in the public or private subnets. __*Default*__: true
   * **nodeCount** (<code>number</code>)  Number of keycloak node in the cluster. __*Default*__: 2
@@ -214,7 +218,9 @@ addKeyCloakContainerService(props: ContainerServiceProps): ContainerService
   * **autoScaleTask** (<code>[AutoScaleTask](#cdk-keycloak-autoscaletask)</code>)  Autoscaling for the ECS Service. __*Default*__: no ecs service autoscaling
   * **bastion** (<code>boolean</code>)  Whether to create the bastion host. __*Default*__: false
   * **circuitBreaker** (<code>boolean</code>)  Whether to enable the ECS service deployment circuit breaker. __*Default*__: false
+  * **containerImage** (<code>[aws_ecs.ContainerImage](#aws-cdk-lib-aws-ecs-containerimage)</code>)  Overrides the default image. __*Default*__: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
   * **env** (<code>Map<string, string></code>)  The environment variables to pass to the keycloak container. __*Optional*__
+  * **hostname** (<code>string</code>)  The hostname to use for the keycloak server. __*Optional*__
   * **internetFacing** (<code>boolean</code>)  Whether to put the put the load balancer in the public or private subnets. __*Default*__: true
   * **nodeCount** (<code>number</code>)  Number of keycloak node in the cluster. __*Default*__: 1
   * **privateSubnets** (<code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code>)  VPC subnets for keycloak service. __*Optional*__
@@ -243,6 +249,12 @@ Name | Type | Description
 *static* **V15_0_1** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 15.0.1.
 *static* **V15_0_2** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 15.0.2.
 *static* **V16_1_1** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 16.1.1.
+*static* **V17_0_1** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 17.0.1.
+*static* **V18_0_3** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 18.0.2.
+*static* **V19_0_3** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 19.0.3.
+*static* **V20_0_3** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 20.0.5.
+*static* **V21_0_0** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 21.0.0.
+*static* **V21_0_1** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | Keycloak version 21.0.1.
 
 ### Methods
 
@@ -294,7 +306,9 @@ Name | Type | Description
 **autoScaleTask**? | <code>[AutoScaleTask](#cdk-keycloak-autoscaletask)</code> | Autoscaling for the ECS Service.<br/>__*Default*__: no ecs service autoscaling
 **bastion**? | <code>boolean</code> | Whether to create the bastion host.<br/>__*Default*__: false
 **circuitBreaker**? | <code>boolean</code> | Whether to enable the ECS service deployment circuit breaker.<br/>__*Default*__: false
+**containerImage**? | <code>[aws_ecs.ContainerImage](#aws-cdk-lib-aws-ecs-containerimage)</code> | Overrides the default image.<br/>__*Default*__: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
 **env**? | <code>Map<string, string></code> | The environment variables to pass to the keycloak container.<br/>__*Optional*__
+**hostname**? | <code>string</code> | The hostname to use for the keycloak server.<br/>__*Optional*__
 **internetFacing**? | <code>boolean</code> | Whether to put the put the load balancer in the public or private subnets.<br/>__*Default*__: true
 **nodeCount**? | <code>number</code> | Number of keycloak node in the cluster.<br/>__*Default*__: 1
 **privateSubnets**? | <code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code> | VPC subnets for keycloak service.<br/>__*Optional*__
@@ -360,12 +374,14 @@ Name | Type | Description
 **backupRetention**? | <code>[Duration](#aws-cdk-lib-duration)</code> | database backup retension.<br/>__*Default*__: 7 days
 **bastion**? | <code>boolean</code> | Create a bastion host for debugging or trouble-shooting.<br/>__*Default*__: false
 **clusterEngine**? | <code>[aws_rds.IClusterEngine](#aws-cdk-lib-aws-rds-iclusterengine)</code> | The database cluster engine.<br/>__*Default*__: rds.AuroraMysqlEngineVersion.VER_2_09_1
+**containerImage**? | <code>[aws_ecs.ContainerImage](#aws-cdk-lib-aws-ecs-containerimage)</code> | Overrides the default image.<br/>__*Default*__: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
 **databaseInstanceType**? | <code>[aws_ec2.InstanceType](#aws-cdk-lib-aws-ec2-instancetype)</code> | Database instance type.<br/>__*Default*__: r5.large
 **databaseMaxCapacity**? | <code>number</code> | The maximum number of Aurora Serverless V2 capacity units.<br/>__*Default*__: 10
 **databaseMinCapacity**? | <code>number</code> | The minimum number of Aurora Serverless V2 capacity units.<br/>__*Default*__: 0.5
 **databaseRemovalPolicy**? | <code>[RemovalPolicy](#aws-cdk-lib-removalpolicy)</code> | Controls what happens to the database if it stops being managed by CloudFormation.<br/>__*Default*__: RemovalPolicy.RETAIN
 **databaseSubnets**? | <code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code> | VPC subnets for database.<br/>__*Default*__: VPC isolated subnets
 **env**? | <code>Map<string, string></code> | The environment variables to pass to the keycloak container.<br/>__*Optional*__
+**hostname**? | <code>string</code> | The hostname to use for the keycloak server.<br/>__*Optional*__
 **instanceEngine**? | <code>[aws_rds.IInstanceEngine](#aws-cdk-lib-aws-rds-iinstanceengine)</code> | The database instance engine.<br/>__*Default*__: MySQL 8.0.21
 **internetFacing**? | <code>boolean</code> | Whether to put the load balancer in the public or private subnets.<br/>__*Default*__: true
 **nodeCount**? | <code>number</code> | Number of keycloak node in the cluster.<br/>__*Default*__: 2
diff --git a/src/keycloak.ts b/src/keycloak.ts
index e15bced..39a5b9f 100644
--- a/src/keycloak.ts
+++ b/src/keycloak.ts
@@ -4,11 +4,12 @@ import {
   aws_ec2 as ec2, aws_ecs as ecs, aws_elasticloadbalancingv2 as elbv2,
   aws_iam as iam,
   aws_logs as logs,
-  aws_rds as rds, aws_secretsmanager as secretsmanager,
+  aws_rds as rds,
+  aws_s3 as s3,
+  aws_secretsmanager as secretsmanager,
 } from 'aws-cdk-lib';
 import { Construct } from 'constructs';
 
-
 // regional availibility for aurora serverless
 // see https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.AuroraFeaturesRegionsDBEngines.grids.html
 const AURORA_SERVERLESS_SUPPORTED_REGIONS = [
@@ -58,6 +59,36 @@ export class KeycloakVersion {
    */
   public static readonly V16_1_1 = KeycloakVersion.of('16.1.1');
 
+  /**
+   * Keycloak version 17.0.1
+   */
+  public static readonly V17_0_1 = KeycloakVersion.of('17.0.1');
+
+  /**
+   * Keycloak version 18.0.2
+   */
+  public static readonly V18_0_3 = KeycloakVersion.of('18.0.2');
+
+  /**
+   * Keycloak version 19.0.3
+   */
+  public static readonly V19_0_3 = KeycloakVersion.of('19.0.3');
+
+  /**
+   * Keycloak version 20.0.5
+   */
+  public static readonly V20_0_3 = KeycloakVersion.of('20.0.5');
+
+  /**
+   * Keycloak version 21.0.0
+   */
+  public static readonly V21_0_0 = KeycloakVersion.of('21.0.0');
+
+  /**
+   * Keycloak version 21.0.1
+   */
+  public static readonly V21_0_1 = KeycloakVersion.of('21.0.1');
+
   /**
    * Custom cluster version
    * @param version custom version number
@@ -76,7 +107,7 @@ interface dockerImageMap {
 }
 
 const KEYCLOAK_DOCKER_IMAGE_URI_MAP: dockerImageMap = {
-  'aws': 'jboss/keycloak:',
+  'aws': 'quay.io/keycloak/keycloak:',
   'aws-cn': '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/dockerhub/jboss/keycloak:',
 };
 
@@ -214,12 +245,19 @@ export interface KeyCloakProps {
    * @default true
    */
   readonly internetFacing?: boolean;
+
+  /**
+   * The hostname to use for the keycloak server
+   */
+  readonly hostname?: string;
+
   /**
    * The minimum number of Aurora Serverless V2 capacity units.
    *
    * @default 0.5
   */
   readonly databaseMinCapacity?: number;
+
   /**
   * The maximum number of Aurora Serverless V2 capacity units.
   *
@@ -233,6 +271,14 @@ export interface KeyCloakProps {
    * @default RemovalPolicy.RETAIN
    */
   readonly databaseRemovalPolicy?: cdk.RemovalPolicy;
+
+
+  /**
+   * Overrides the default image
+   *
+   * @default quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
+   */
+  readonly containerImage?: ecs.ContainerImage;
 }
 
 export class KeyCloak extends Construct {
@@ -252,6 +298,7 @@ export class KeyCloak extends Construct {
 
     this.keycloakSecret = this._generateKeycloakSecret();
     this.vpc = props.vpc ?? getOrCreateVpc(this);
+
     this.db = this.addDatabase({
       vpc: this.vpc,
       databaseSubnets: props.databaseSubnets,
@@ -280,7 +327,10 @@ export class KeyCloak extends Construct {
       autoScaleTask: props.autoScaleTask,
       env: props.env,
       internetFacing: props.internetFacing ?? true,
+      hostname: props.hostname,
+      containerImage: props.containerImage,
     });
+
     this.applicationLoadBalancer = keycloakContainerService.applicationLoadBalancer;
     if (!cdk.Stack.of(this).templateOptions.description) {
       cdk.Stack.of(this).templateOptions.description = '(SO8021) - Deploy keycloak on AWS with cdk-keycloak construct library';
@@ -615,6 +665,19 @@ export interface ContainerServiceProps {
    * @default true
    */
   readonly internetFacing?: boolean;
+
+  /**
+   * The hostname to use for the keycloak server
+   */
+  readonly hostname?: string;
+
+
+  /**
+   * Overrides the default image
+   *
+   * @default quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
+   */
+  readonly containerImage?: ecs.ContainerImage;
 }
 
 export class ContainerService extends Construct {
@@ -623,10 +686,78 @@ export class ContainerService extends Construct {
   constructor(scope: Construct, id: string, props: ContainerServiceProps) {
     super(scope, id);
 
+    const region = cdk.Stack.of(this).region;
+    let containerPort = 8443;
+    let protocol = elbv2.ApplicationProtocol.HTTPS;
+    let command = undefined;
+    let s3PingBucket: s3.Bucket | undefined = undefined;
+    const image = props.containerImage ?? ecs.ContainerImage.fromRegistry(this.getKeyCloakDockerImageUri(props.keycloakVersion.version));
+    const isQuarkusDistribution = parseInt(props.keycloakVersion.version.split('.')[0]) > 16;
+    let environment: {[key: string]: string} = {
+      DB_ADDR: props.database.clusterEndpointHostname,
+      DB_DATABASE: 'keycloak',
+      DB_PORT: '3306',
+      DB_USER: 'admin',
+      DB_VENDOR: 'mysql',
+      JDBC_PARAMS: 'useSSL=false',
+      JGROUPS_DISCOVERY_PROTOCOL: 'JDBC_PING',
+      // We don't need to specify `initialize_sql` string into `JGROUPS_DISCOVERY_PROPERTIES` property,
+      // because the default `initialize_sql` is compatible with MySQL. (See: https://github.com/belaban/JGroups/blob/master/src/org/jgroups/protocols/JDBC_PING.java#L55-L60)
+      // But you need to specify `initialize_sql` for PostgreSQL, because `varbinary` schema is not supported. (See: https://github.com/keycloak/keycloak-containers/blob/d4ce446dde3026f89f66fa86b58c2d0d6132ce4d/docker-compose-examples/keycloak-postgres-jdbc-ping.yml#L49)
+      // JGROUPS_DISCOVERY_PROPERTIES: '',
+      // KEYCLOAK_LOGLEVEL: 'DEBUG',
+      PROXY_ADDRESS_FORWARDING: 'true',
+    };
+    let secrets: {[key: string]: cdk.aws_ecs.Secret} = {
+      DB_PASSWORD: ecs.Secret.fromSecretsManager(props.database.secret, 'password'),
+      KEYCLOAK_USER: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'username'),
+      KEYCLOAK_PASSWORD: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'password'),
+    };
+    let portMappings: ecs.PortMapping[] = [
+      { containerPort: containerPort }, // HTTPS web port
+      { containerPort: 7600 }, // jgroups-tcp
+      { containerPort: 57600 }, // jgroups-tcp-fd
+      { containerPort: 55200, protocol: ecs.Protocol.UDP }, // jgroups-udp
+      { containerPort: 54200, protocol: ecs.Protocol.UDP }, // jgroups-udp-fd
+    ];
+
+    // if this is a quarkus distribution
+    if (isQuarkusDistribution) {
+      s3PingBucket = new s3.Bucket(this, 'keycloak_s3_ping');
+      containerPort = 8080;
+      protocol = elbv2.ApplicationProtocol.HTTP;
+      command = ['start', '--optimized'];
+      environment = {
+        JAVA_OPTS_APPEND: `-Djgroups.s3.region_name=${region} -Djgroups.s3.bucket_name=${s3PingBucket!.bucketName}`,
+        // We have selected the cache stack of 'ec2' which uses S3_PING under the hood
+        // This is the AWS native cluster discovery approach for caching
+        // See: https://www.keycloak.org/server/caching#_transport_stacks
+        KC_CACHE_STACK: 'ec2',
+        KC_DB: 'mysql',
+        KC_DB_URL_DATABASE: 'keycloak',
+        KC_DB_URL_HOST: props.database.clusterEndpointHostname,
+        KC_DB_URL_PORT: '3306',
+        KC_DB_USERNAME: 'admin',
+        KC_HOSTNAME: props.hostname!,
+        KC_HOSTNAME_STRICT_BACKCHANNEL: 'true',
+        KC_PROXY: 'edge',
+      };
+      secrets = {
+        KC_DB_PASSWORD: ecs.Secret.fromSecretsManager(props.database.secret, 'password'),
+        KEYCLOAK_ADMIN: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'username'),
+        KEYCLOAK_ADMIN_PASSWORD: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'password'),
+      };
+      portMappings = [
+        { containerPort: containerPort }, // web port
+        { containerPort: 7800 }, // jgroups-s3
+        { containerPort: 57800 }, // jgroups-s3-fd
+      ];
+    }
+
     const vpc = props.vpc;
     const cluster = new ecs.Cluster(this, 'Cluster', { vpc, containerInsights: true });
     cluster.node.addDependency(props.database);
-    const taskRole = new iam.Role(this, 'TaskRole', {
+    const executionRole = new iam.Role(this, 'TaskRole', {
       assumedBy: new iam.CompositePrincipal(
         new iam.ServicePrincipal('ecs.amazonaws.com'),
         new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
@@ -635,7 +766,7 @@ export class ContainerService extends Construct {
     const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
       cpu: 4096,
       memoryLimitMiB: 8192,
-      executionRole: taskRole,
+      executionRole,
     });
 
     const logGroup = new logs.LogGroup(this, 'LogGroup', {
@@ -644,39 +775,16 @@ export class ContainerService extends Construct {
     });
 
     const kc = taskDefinition.addContainer('keycloak', {
-      image: ecs.ContainerImage.fromRegistry(this.getKeyCloakDockerImageUri(props.keycloakVersion.version)),
-      environment: Object.assign({
-        DB_ADDR: props.database.clusterEndpointHostname,
-        DB_DATABASE: 'keycloak',
-        DB_PORT: '3306',
-        DB_USER: 'admin',
-        DB_VENDOR: 'mysql',
-        // KEYCLOAK_LOGLEVEL: 'DEBUG',
-        PROXY_ADDRESS_FORWARDING: 'true',
-        JDBC_PARAMS: 'useSSL=false',
-        JGROUPS_DISCOVERY_PROTOCOL: 'JDBC_PING',
-        // We don't need to specify `initialize_sql` string into `JGROUPS_DISCOVERY_PROPERTIES` property,
-        // because the default `initialize_sql` is compatible with MySQL. (See: https://github.com/belaban/JGroups/blob/master/src/org/jgroups/protocols/JDBC_PING.java#L55-L60)
-        // But you need to specify `initialize_sql` for PostgreSQL, because `varbinary` schema is not supported. (See: https://github.com/keycloak/keycloak-containers/blob/d4ce446dde3026f89f66fa86b58c2d0d6132ce4d/docker-compose-examples/keycloak-postgres-jdbc-ping.yml#L49)
-        // JGROUPS_DISCOVERY_PROPERTIES: '',
-      }, props.env),
-      secrets: {
-        DB_PASSWORD: ecs.Secret.fromSecretsManager(props.database.secret, 'password'),
-        KEYCLOAK_USER: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'username'),
-        KEYCLOAK_PASSWORD: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'password'),
-      },
+      image,
+      command,
+      environment: Object.assign(environment, props.env),
+      secrets,
       logging: ecs.LogDrivers.awsLogs({
         streamPrefix: 'keycloak',
         logGroup,
       }),
     });
-    kc.addPortMappings(
-      { containerPort: 8443 }, // HTTPS web port
-      { containerPort: 7600 }, // jgroups-tcp
-      { containerPort: 57600 }, // jgroups-tcp-fd
-      { containerPort: 55200, protocol: ecs.Protocol.UDP }, // jgroups-udp
-      { containerPort: 54200, protocol: ecs.Protocol.UDP }, // jgroups-udp-fd
-    );
+    kc.addPortMappings(...portMappings);
 
     // we need extra privileges to fetch keycloak docker images from China mirror site
     taskDefinition.executionRole?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'));
@@ -689,10 +797,16 @@ export class ContainerService extends Construct {
       healthCheckGracePeriod: cdk.Duration.seconds(120),
     });
     // we need to allow traffic from the same secret group for keycloak cluster with jdbc_ping
-    this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(7600), 'kc jgroups-tcp');
-    this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(57600), 'kc jgroups-tcp-fd');
-    this.service.connections.allowFrom(this.service.connections, ec2.Port.udp(55200), 'kc jgroups-udp');
-    this.service.connections.allowFrom(this.service.connections, ec2.Port.udp(54200), 'kc jgroups-udp-fd');
+    if (isQuarkusDistribution) {
+      this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(7800), 'kc jgroups-tcp');
+      this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(57800), 'kc jgroups-tcp-fd');
+      s3PingBucket!.grantReadWrite(taskDefinition.taskRole);
+    } else {
+      this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(7600), 'kc jgroups-tcp');
+      this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(57600), 'kc jgroups-tcp-fd');
+      this.service.connections.allowFrom(this.service.connections, ec2.Port.udp(55200), 'kc jgroups-udp');
+      this.service.connections.allowFrom(this.service.connections, ec2.Port.udp(54200), 'kc jgroups-udp-fd');
+    }
 
     if (props.autoScaleTask) {
       const minCapacity = props.autoScaleTask.min ?? props.nodeCount ?? 2;
@@ -716,7 +830,6 @@ export class ContainerService extends Construct {
       protocol: elbv2.ApplicationProtocol.HTTPS,
       certificates: [{ certificateArn: props.certificate.certificateArn }],
     });
-
     listener.addTargets('ECSTarget', {
       targets: [this.service],
       healthCheck: {
@@ -726,8 +839,8 @@ export class ContainerService extends Construct {
       // see https://docs.aws.amazon.com/cli/latest/reference/elbv2/modify-target-group-attributes.html
       slowStart: cdk.Duration.seconds(60),
       stickinessCookieDuration: props.stickinessCookieDuration ?? cdk.Duration.days(1),
-      port: 8443,
-      protocol: elbv2.ApplicationProtocol.HTTPS,
+      port: containerPort,
+      protocol,
     });
 
     // allow task execution role to read the secrets


Test patch:
diff --git a/test/__snapshots__/integ.snapshot.test.ts.snap b/test/__snapshots__/integ.snapshot.test.ts.snap
index 386d262..3672ad5 100644
--- a/test/__snapshots__/integ.snapshot.test.ts.snap
+++ b/test/__snapshots__/integ.snapshot.test.ts.snap
@@ -641,10 +641,6 @@ Object {
                 "Name": "DB_VENDOR",
                 "Value": "mysql",
               },
-              Object {
-                "Name": "PROXY_ADDRESS_FORWARDING",
-                "Value": "true",
-              },
               Object {
                 "Name": "JDBC_PARAMS",
                 "Value": "useSSL=false",
@@ -653,9 +649,13 @@ Object {
                 "Name": "JGROUPS_DISCOVERY_PROTOCOL",
                 "Value": "JDBC_PING",
               },
+              Object {
+                "Name": "PROXY_ADDRESS_FORWARDING",
+                "Value": "true",
+              },
             ],
             "Essential": true,
-            "Image": "jboss/keycloak:15.0.215.0.2",
+            "Image": "quay.io/keycloak/keycloak:15.0.215.0.2",
             "LogConfiguration": Object {
               "LogDriver": "awslogs",
               "Options": Object {
diff --git a/test/cluster.test.ts.orig b/test/cluster-quarkus.test.ts
similarity index 78%
rename from test/cluster.test.ts.orig
rename to test/cluster-quarkus.test.ts
index 4d01fb5..9b09a0e 100644
--- a/test/cluster.test.ts.orig
+++ b/test/cluster-quarkus.test.ts
@@ -1,9 +1,8 @@
-import { App, Stack } from '@aws-cdk/core';
+import { App, assertions, Stack } from 'aws-cdk-lib';
 import * as kc from '../src';
-import '@aws-cdk/assert/jest';
+// import '@aws-cdk/assert/jest';
 import { KeycloakVersion } from '../src';
 test('create the default cluster', () => {
-
   // GIVEN
   const app = new App();
   const stack = new Stack(app, 'testing-stack');
@@ -11,11 +10,12 @@ test('create the default cluster', () => {
   // WHEN
   new kc.KeyCloak(stack, 'KeyCloak', {
     certificateArn: 'MOCK_ARN',
-    keycloakVersion: KeycloakVersion.V15_0_2,
+    keycloakVersion: KeycloakVersion.V21_0_1,
   });
 
   // THEN
-  expect(stack).toHaveResource('AWS::RDS::DBCluster', {
+  const t = assertions.Template.fromStack(stack);
+  t.hasResourceProperties('AWS::RDS::DBCluster', {
     Engine: 'aurora-mysql',
     DBClusterParameterGroupName: 'default.aurora-mysql5.7',
     DBSubnetGroupName: {
@@ -37,19 +37,16 @@ test('create the default cluster', () => {
     },
     VpcSecurityGroupIds: [
       {
-        'Fn::GetAtt': [
-          'KeyCloakDatabaseDBClusterSecurityGroup843B4392',
-          'GroupId',
-        ],
+        'Fn::GetAtt': ['KeyCloakDatabaseDBClusterSecurityGroup843B4392', 'GroupId'],
       },
     ],
   });
   // we should have 2 db instances in the cluster
-  expect(stack).toCountResources('AWS::RDS::DBInstance', 2);
+  t.resourceCountIs('AWS::RDS::DBInstance', 2);
   // we should have 2 secrets
-  expect(stack).toCountResources('AWS::SecretsManager::Secret', 2);
+  t.resourceCountIs('AWS::SecretsManager::Secret', 2);
   // we should have ecs service
-  expect(stack).toHaveResource('AWS::ECS::Service', {
+  t.hasResourceProperties('AWS::ECS::Service', {
     Cluster: {
       Ref: 'KeyCloakKeyCloakContainerSerivceClusterA18E44FF',
     },
@@ -64,7 +61,7 @@ test('create the default cluster', () => {
     LoadBalancers: [
       {
         ContainerName: 'keycloak',
-        ContainerPort: 8443,
+        ContainerPort: 8080,
         TargetGroupArn: {
           Ref: 'KeyCloakKeyCloakContainerSerivceALBHttpsListenerECSTargetGroupCE3EF52C',
         },
@@ -98,7 +95,6 @@ test('create the default cluster', () => {
 });
 
 test('with aurora serverless', () => {
-
   // GIVEN
   const app = new App();
   const stack = new Stack(app, 'testing-stack');
@@ -107,21 +103,22 @@ test('with aurora serverless', () => {
   new kc.KeyCloak(stack, 'KeyCloak', {
     certificateArn: 'MOCK_ARN',
     auroraServerless: true,
-    keycloakVersion: KeycloakVersion.V15_0_2,
+    keycloakVersion: KeycloakVersion.V21_0_1,
   });
 
   // THEN
-  expect(stack).toHaveResource('AWS::RDS::DBCluster', {
+  const t = assertions.Template.fromStack(stack);
+  t.hasResourceProperties('AWS::RDS::DBCluster', {
     Engine: 'aurora-mysql',
     DBClusterParameterGroupName: 'default.aurora-mysql5.7',
     EngineMode: 'serverless',
   });
   // we should have 0 db instance in the cluster
-  expect(stack).toCountResources('AWS::RDS::DBInstance', 0);
+  t.resourceCountIs('AWS::RDS::DBInstance', 0);
   // we should have 2 secrets
-  expect(stack).toCountResources('AWS::SecretsManager::Secret', 2);
+  t.resourceCountIs('AWS::SecretsManager::Secret', 2);
   // we should have ecs service
-  expect(stack).toHaveResource('AWS::ECS::Service', {
+  t.hasResourceProperties('AWS::ECS::Service', {
     Cluster: {
       Ref: 'KeyCloakKeyCloakContainerSerivceClusterA18E44FF',
     },
@@ -136,7 +133,7 @@ test('with aurora serverless', () => {
     LoadBalancers: [
       {
         ContainerName: 'keycloak',
-        ContainerPort: 8443,
+        ContainerPort: 8080,
         TargetGroupArn: {
           Ref: 'KeyCloakKeyCloakContainerSerivceALBHttpsListenerECSTargetGroupCE3EF52C',
         },
@@ -170,7 +167,6 @@ test('with aurora serverless', () => {
 });
 
 test('with aurora serverless v2', () => {
-
   // GIVEN
   const app = new App();
   const stack = new Stack(app, 'testing-stack');
@@ -179,11 +175,12 @@ test('with aurora serverless v2', () => {
   new kc.KeyCloak(stack, 'KeyCloak', {
     certificateArn: 'MOCK_ARN',
     auroraServerlessV2: true,
-    keycloakVersion: KeycloakVersion.V15_0_2,
+    keycloakVersion: KeycloakVersion.V21_0_1,
   });
 
   // THEN
-  expect(stack).toHaveResource('AWS::RDS::DBCluster', {
+  const t = assertions.Template.fromStack(stack);
+  t.hasResourceProperties('AWS::RDS::DBCluster', {
     Engine: 'aurora-mysql',
     DBClusterParameterGroupName: 'default.aurora-mysql8.0',
     DBSubnetGroupName: {
@@ -209,23 +206,20 @@ test('with aurora serverless v2', () => {
     },
     VpcSecurityGroupIds: [
       {
-        'Fn::GetAtt': [
-          'KeyCloakDatabaseDBClusterSecurityGroup843B4392',
-          'GroupId',
-        ],
+        'Fn::GetAtt': ['KeyCloakDatabaseDBClusterSecurityGroup843B4392', 'GroupId'],
       },
     ],
   });
   // we should have 2 db instances in the cluster
-  expect(stack).toCountResources('AWS::RDS::DBInstance', 2);
+  t.resourceCountIs('AWS::RDS::DBInstance', 2);
   // we should have db instance with db.serverless instance class
-  expect(stack).toHaveResource('AWS::RDS::DBInstance', {
+  t.hasResourceProperties('AWS::RDS::DBInstance', {
     DBInstanceClass: 'db.serverless',
   });
   // we should have 2 secrets
-  expect(stack).toCountResources('AWS::SecretsManager::Secret', 2);
+  t.resourceCountIs('AWS::SecretsManager::Secret', 2);
   // we should have ecs service
-  expect(stack).toHaveResource('AWS::ECS::Service', {
+  t.hasResourceProperties('AWS::ECS::Service', {
     Cluster: {
       Ref: 'KeyCloakKeyCloakContainerSerivceClusterA18E44FF',
     },
@@ -240,7 +234,7 @@ test('with aurora serverless v2', () => {
     LoadBalancers: [
       {
         ContainerName: 'keycloak',
-        ContainerPort: 8443,
+        ContainerPort: 8080,
         TargetGroupArn: {
           Ref: 'KeyCloakKeyCloakContainerSerivceALBHttpsListenerECSTargetGroupCE3EF52C',
         },
@@ -274,7 +268,6 @@ test('with aurora serverless v2', () => {
 });
 
 test('with single rds instance', () => {
-
   // GIVEN
   const app = new App();
   const stack = new Stack(app, 'testing-stack');
@@ -283,15 +276,16 @@ test('with single rds instance', () => {
   new kc.KeyCloak(stack, 'KeyCloak', {
     certificateArn: 'MOCK_ARN',
     singleDbInstance: true,
-    keycloakVersion: KeycloakVersion.V15_0_2,
+    keycloakVersion: KeycloakVersion.V21_0_1,
   });
 
   // THEN
+  const t = assertions.Template.fromStack(stack);
   // we should have no cluster
-  expect(stack).toCountResources('AWS::RDS::DBCluster', 0);
+  t.resourceCountIs('AWS::RDS::DBCluster', 0);
   // we should have 1 db instance in the cluster
-  expect(stack).toCountResources('AWS::RDS::DBInstance', 1);
-  expect(stack).toHaveResource('AWS::RDS::DBInstance', {
+  t.resourceCountIs('AWS::RDS::DBInstance', 1);
+  t.hasResourceProperties('AWS::RDS::DBInstance', {
     DBInstanceClass: 'db.r5.large',
     AllocatedStorage: '100',
     CopyTagsToSnapshot: true,
@@ -317,17 +311,14 @@ test('with single rds instance', () => {
     StorageType: 'gp2',
     VPCSecurityGroups: [
       {
-        'Fn::GetAtt': [
-          'KeyCloakDatabaseDBInstanceSecurityGroupC897947D',
-          'GroupId',
-        ],
+        'Fn::GetAtt': ['KeyCloakDatabaseDBInstanceSecurityGroupC897947D', 'GroupId'],
       },
     ],
   });
   // we should have 2 secrets
-  expect(stack).toCountResources('AWS::SecretsManager::Secret', 2);
+  t.resourceCountIs('AWS::SecretsManager::Secret', 2);
   // we should have ecs service
-  expect(stack).toHaveResource('AWS::ECS::Service', {
+  t.hasResourceProperties('AWS::ECS::Service', {
     Cluster: {
       Ref: 'KeyCloakKeyCloakContainerSerivceClusterA18E44FF',
     },
@@ -342,7 +333,7 @@ test('with single rds instance', () => {
     LoadBalancers: [
       {
         ContainerName: 'keycloak',
-        ContainerPort: 8443,
+        ContainerPort: 8080,
         TargetGroupArn: {
           Ref: 'KeyCloakKeyCloakContainerSerivceALBHttpsListenerECSTargetGroupCE3EF52C',
         },
@@ -382,55 +373,60 @@ test('with env', () => {
 
   // WHEN
   new kc.KeyCloak(stack, 'KeyCloak', {
-    keycloakVersion: KeycloakVersion.V15_0_2,
+    keycloakVersion: KeycloakVersion.V21_0_1,
     certificateArn: 'MOCK_ARN',
     env: {
       JAVA_OPTS: '-DHelloWorld',
     },
+    hostname: 'keycloak.test',
   });
 
   // THEN
-  expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', {
-
+  const t = assertions.Template.fromStack(stack);
+  t.hasResourceProperties('AWS::ECS::TaskDefinition', {
     ContainerDefinitions: [
       {
         Environment: [
           {
-            Name: 'DB_ADDR',
-            Value: {
-              'Fn::GetAtt': [
-                'KeyCloakDatabaseDBCluster06E9C0E1',
-                'Endpoint.Address',
-              ],
-            },
+            Name: 'JAVA_OPTS_APPEND',
+          },
+          {
+            Name: 'KC_CACHE_STACK',
+            Value: 'ec2',
+          },
+          {
+            Name: 'KC_DB',
+            Value: 'mysql',
           },
           {
-            Name: 'DB_DATABASE',
+            Name: 'KC_DB_URL_DATABASE',
             Value: 'keycloak',
           },
           {
-            Name: 'DB_PORT',
+            Name: 'KC_DB_URL_HOST',
+            Value: {
+              'Fn::GetAtt': ['KeyCloakDatabaseDBCluster06E9C0E1', 'Endpoint.Address'],
+            },
+          },
+          {
+            Name: 'KC_DB_URL_PORT',
             Value: '3306',
           },
           {
-            Name: 'DB_USER',
+            Name: 'KC_DB_USERNAME',
             Value: 'admin',
           },
           {
-            Name: 'DB_VENDOR',
-            Value: 'mysql',
+            Name: 'KC_HOSTNAME',
+            Value: 'keycloak.test',
           },
           {
-            Name: 'PROXY_ADDRESS_FORWARDING',
+            Name: 'KC_HOSTNAME_STRICT_BACKCHANNEL',
             Value: 'true',
           },
           {
-            Name: 'JDBC_PARAMS',
-            Value: 'useSSL=false',
-          },
-          {
-            Name: 'JGROUPS_DISCOVERY_PROTOCOL',
-            Value: 'JDBC_PING',
+            Name: 'KC_PROXY',
+            Value: 'edge',
           },
           {
             Name: 'JAVA_OPTS',
@@ -462,29 +458,21 @@ test('with env', () => {
         Name: 'keycloak',
         PortMappings: [
           {
-            ContainerPort: 8443,
+            ContainerPort: 8080,
             Protocol: 'tcp',
           },
           {
-            ContainerPort: 7600,
+            ContainerPort: 7800,
             Protocol: 'tcp',
           },
           {
-            ContainerPort: 57600,
+            ContainerPort: 57800,
             Protocol: 'tcp',
           },
-          {
-            ContainerPort: 55200,
-            Protocol: 'udp',
-          },
-          {
-            ContainerPort: 54200,
-            Protocol: 'udp',
-          },
         ],
         Secrets: [
           {
-            Name: 'DB_PASSWORD',
+            Name: 'KC_DB_PASSWORD',
             ValueFrom: {
               'Fn::Join': [
                 '',
@@ -498,7 +486,7 @@ test('with env', () => {
             },
           },
           {
-            Name: 'KEYCLOAK_USER',
+            Name: 'KEYCLOAK_ADMIN',
             ValueFrom: {
               'Fn::Join': [
                 '',
@@ -512,7 +500,7 @@ test('with env', () => {
             },
           },
           {
-            Name: 'KEYCLOAK_PASSWORD',
+            Name: 'KEYCLOAK_ADMIN_PASSWORD',
             ValueFrom: {
               'Fn::Join': [
                 '',
@@ -530,22 +518,14 @@ test('with env', () => {
     ],
     Cpu: '4096',
     ExecutionRoleArn: {
-      'Fn::GetAtt': [
-        'KeyCloakKeyCloakContainerSerivceTaskRole0658CED2',
-        'Arn',
-      ],
+      'Fn::GetAtt': ['KeyCloakKeyCloakContainerSerivceTaskRole0658CED2', 'Arn'],
     },
     Family: 'testingstackKeyCloakKeyCloakContainerSerivceTaskDef799BAD5B',
     Memory: '8192',
     NetworkMode: 'awsvpc',
-    RequiresCompatibilities: [
-      'FARGATE',
-    ],
+    RequiresCompatibilities: ['FARGATE'],
     TaskRoleArn: {
-      'Fn::GetAtt': [
-        'KeyCloakKeyCloakContainerSerivceTaskDefTaskRole0DC4D418',
-        'Arn',
-      ],
+      'Fn::GetAtt': ['KeyCloakKeyCloakContainerSerivceTaskDefTaskRole0DC4D418', 'Arn'],
     },
   });
 });
diff --git a/test/cluster.test.ts b/test/cluster-wildfly.test.ts
similarity index 99%
rename from test/cluster.test.ts
rename to test/cluster-wildfly.test.ts
index a8e9012..e2d007d 100644
--- a/test/cluster.test.ts
+++ b/test/cluster-wildfly.test.ts
@@ -1,4 +1,4 @@
-import { App, Stack, assertions } from 'aws-cdk-lib';
+import { App, assertions, Stack } from 'aws-cdk-lib';
 import * as kc from '../src';
 // import '@aws-cdk/assert/jest';
 import { KeycloakVersion } from '../src';
@@ -425,10 +425,6 @@ test('with env', () => {
             Name: 'DB_VENDOR',
             Value: 'mysql',
           },
-          {
-            Name: 'PROXY_ADDRESS_FORWARDING',
-            Value: 'true',
-          },
           {
             Name: 'JDBC_PARAMS',
             Value: 'useSSL=false',
@@ -437,6 +433,10 @@ test('with env', () => {
             Name: 'JGROUPS_DISCOVERY_PROTOCOL',
             Value: 'JDBC_PING',
           },
+          {
+            Name: 'PROXY_ADDRESS_FORWARDING',
+            Value: 'true',
+          },
           {
             Name: 'JAVA_OPTS',
             Value: '-DHelloWorld',