· TL;DR
· Architecture Overview
∘ Key Components:
· Step 1: Setting Up the AWS Environment
∘ 1.1: Create a VPC
∘ 1.2: Create Subnets
· Step 2: Configure Security Groups
∘ 2.1: Security Group for ECS
· Step 3: Set Up the ECS Cluster
· Step 4: Configure AWS RDS for Keycloak
· Step 5: Create an ECS Task Definition for Keycloak
· Step 6: Deploy ECS Service
· Step 7: Set Up Auto Scaling
∘ 7.1: CPU Auto Scaling Policy
· Step 8: Configure ALB for Secure Access
∘ AWS Application Load Balancer (ALB)
∘ Target Group (Attaches ECS Containers to ALB)
· Step 9: Deploy and Test
TL;DR
You can find deployment ready solution here — https://github.com/metronom72/keycloak_deployment/tree/main
Architecture Overview
Key Components:
- AWS ECR (Elastic Container Registry) for storing container images.
- AWS ECS Cluster running Keycloak as a Fargate task.
- AWS RDS (PostgreSQL) as the database backend.
- AWS ALB (Application Load Balancer) for secure access.
- AWS CloudWatch for logging.
- AWS IAM Roles for permissions.
- AWS Auto Scaling Policies for optimizing performance.
Step 1: Setting Up the AWS Environment
1.1: Create a VPC
resource "aws_vpc" "keycloak" {
cidr_block = "10.0.0.0/16"
}
A VPC (Virtual Private Cloud) is a private network within AWS where you can launch resources (e.g., EC2 instances, databases). It allows you to control:
- IP addressing (e.g., private and public subnets)
- Network access (firewalls, security groups, and route tables)
- Connectivity (VPNs, peering, and gateways)
This specifies the CIDR (Classless Inter-Domain Routing) block for the VPC. 10.0.0.0/16
means:
- The network starts at
10.0.0.0
. - The /16 means it includes IP addresses from
10.0.0.0
to10.0.255.255
(65,536
addresses). - This allows subnetting within the VPC.
1.2: Create Subnets
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.keycloak.id
cidr_block = "10.0.${count.index}.0/24"
availability_zone = element(["eu-central-1a", "eu-central-1b"], count.index)
}
A subnet (subnetwork) is a segment of a VPC that groups resources (e.g., EC2 instances) into smaller networks. Subnets help with: Organizing resources (e.g., private vs. public subnets)
- Two subnets allow redundancy (if one AZ fails, the other is still running).
- Private subnets keep internal resources (e.g., databases) hidden from the public internet.
- CIDR allocation ensures that each subnet gets a unique range of IP addresses.
This Terraform block defines two private subnets inside the VPC (aws_vpc.keycloak
). Let's break it down step by step:
count = 2
This tells Terraform to create two subnets instead of just one.
Each subnet will have a different index (0
for the first, 1
for the second).
2. vpc_id = aws_vpc.keycloak.id
This links each subnet to the VPC named “keycloak”.aws_vpc.keycloak.id
retrieves the ID of the previously defined VPC.
3. cidr_block = "10.0.${count.index}.0/24"
This assigns a unique CIDR block (IP range) to each subnet.
The ${count.index}
dynamically inserts 0
for the first subnet and 1
for the second:
- First subnet:
10.0.0.0/24
(256 IP addresses: 10.0.0.0 – 10.0.0.255) - Second subnet:
10.0.1.0/24
(256 IP addresses: 10.0.1.0 – 10.0.1.255)
4. availability_zone = element([“eu-central-1a”, “eu-central-1b”], count.index)
Step 2: Configure Security Groups
2.1: Security Group for ECS
resource "aws_security_group" "ecs_sg" {
vpc_id = aws_vpc.keycloak.id
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
A security group in AWS acts as a virtual firewall for controlling inbound and outbound traffic for your resources (such as EC2 instances or ECS tasks).
1. resource "aws_security_group" "ecs_sg"
This defines an AWS Security Group named ecs_sg
in Terraform.
2. vpc_id = aws_vpc.keycloak.id
This security group is attached to the VPC named “keycloak”.
3. ingress { ... }
(Inbound Rules)
This rule allows ALL inbound traffic from anywhere in the world (0.0.0.0/0
).from_port = 0, to_port = 0
and protocol = "-1"
mean all protocols and ports are open.
4. egress { ... }
(Outbound Rules)
This rule allows ALL outbound traffic to any destination (0.0.0.0/0
).
Like ingress, from_port = 0, to_port = 0, protocol = "-1"
means all traffic is allowed.
Step 3: Set Up the ECS Cluster
resource "aws_ecs_cluster" "keycloak_cluster" {
name = "keycloak-cluster"
}
An ECS (Elastic Container Service) cluster is a logical grouping of containerized applications running on AWS. It allows you to deploy, manage, and scale Docker containers using AWS Fargate (serverless) or EC2 instances (self-managed).
1. resource “aws_ecs_cluster” “keycloak_cluster”
This creates an ECS cluster in AWS.
“keycloak_cluster” is a Terraform identifier (used for referencing within the configuration).
2. name = “keycloak-cluster”
This assigns the name “keycloak-cluster” to the ECS cluster.
You will see this name in the AWS ECS Console under “Clusters”.
Step 4: Configure AWS RDS for Keycloak
resource "aws_db_instance" "postgres" {
identifier = "keycloak-db"
engine = "postgres"
instance_class = "db.t3.micro"
allocated_storage = 20
username = "admin"
password = "securepassword"
publicly_accessible = false
}
AWS RDS (Relational Database Service) is a managed database service that automates setup, scaling, backups, and maintenance. This code creates a PostgreSQL database instance on AWS.
1. resource "aws_db_instance" "postgres"
- This creates an AWS RDS database instance.
"postgres"
is a Terraform identifier for internal use.
2. identifier = "keycloak-db"
- This sets the database instance name to
"keycloak-db"
(visible in AWS Console).
3. engine = "postgres"
- This specifies that the database will run PostgreSQL.
4. instance_class = "db.t3.micro"
- Defines the instance type (CPU, memory, networking):
"db.t3.micro"
is a small and cost-effective instance.- Suitable for development/testing, but not for high-traffic production.
5. allocated_storage = 20
- Sets the storage size to 20GB.
- Can be increased but not decreased after creation.
6. username = "admin"
& password = "securepassword"
- Defines the database administrator credentials.
- Then, define
db_password
in a Terraform variable file or use AWS Secrets Manager.
7. publicly_accessible = false
- The database CANNOT be accessed from the public internet (good for security).
- Only resources inside the VPC can connect.
Step 5: Create an ECS Task Definition for Keycloak
resource "aws_ecs_task_definition" "keycloak" {
family = "keycloak-task"
requires_compatibilities = ["FARGATE"]
memory = "2048"
cpu = "1024"
execution_role_arn = aws_iam_role.ecsTaskExecutionRole.arn
task_role_arn = aws_iam_role.ecsTaskExecutionRole.arn
network_mode = "awsvpc"
container_definitions = jsonencode([
{
name = "keycloak"
image = "${aws_ecr_repository.keycloak.repository_url}:latest"
memory = 2048
cpu = 1024
essential = true
portMappings = [
{ containerPort = 8443, protocol = "tcp" },
{ containerPort = 8080, protocol = "tcp" }
]
environment = [
{ name = "KC_DB", value = "postgres" },
{ name = "KC_DB_URL", value = "jdbc:postgresql://${aws_db_instance.postgres.endpoint}/keycloak" },
{ name = "KC_HOSTNAME", value = "keycloak.example.com" }
]
secrets = [
{ name = "KEYCLOAK_ADMIN", valueFrom = "${aws_secretsmanager_secret.keycloak_admin.arn}:username::" },
{ name = "KEYCLOAK_ADMIN_PASSWORD", valueFrom = "${aws_secretsmanager_secret.keycloak_admin.arn}:password::" }
]
}
])
}
An ECS Task Definition defines how a containerized application should run in AWS ECS. It includes CPU, memory, container image, environment variables, ports, and secrets.
- ECS starts 2 Keycloak containers using AWS Fargate.
- Containers run inside private subnets.
- The load balancer distributes incoming traffic to healthy Keycloak containers.
- If a container crashes, ECS replaces it automatically.
- Security groups ensure controlled network access.
Step 6: Deploy ECS Service
resource "aws_ecs_service" "keycloak_service" {
name = "keycloak-service"
cluster = aws_ecs_cluster.keycloak_cluster.id
task_definition = "${aws_ecs_task_definition.keycloak.family}:${aws_ecs_task_definition.keycloak.revision}"
launch_type = "FARGATE"
desired_count = 2
network_configuration {
subnets = aws_subnet.private[*].id
security_groups = [aws_security_group.ecs_sg.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.keycloak_tg.arn
container_name = "keycloak"
container_port = 8443
}
}
An ECS Service is responsible for running and managing multiple copies (tasks) of a containerized application.
It ensures:
- Automatic scaling (if a container stops, it restarts).
- Load balancing (distributes traffic across running tasks).
- Integration with networking (subnets, security groups).
Step 7: Set Up Auto Scaling
resource "aws_appautoscaling_target" "ecs_scaling" {
resource_id = "service/${aws_ecs_cluster.keycloak_cluster.name}/${aws_ecs_service.keycloak_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
min_capacity = 2
max_capacity = 10
}
7.1: CPU Auto Scaling Policy
resource "aws_appautoscaling_policy" "cpu_scaling" {
name = "cpu-scaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs_scaling.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_scaling.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_scaling.service_namespace
}
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 60.0
scale_in_cooldown = 60
scale_out_cooldown = 60
}
}
Step 8: Configure ALB for Secure Access
resource "aws_lb" "keycloak_alb" {
name = "keycloak-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.ecs_sg.id]
subnets = aws_subnet.private[*].id
}
resource "aws_lb_target_group" "keycloak_tg" {
name = "keycloak-tg"
port = 8443
protocol = "HTTPS"
vpc_id = aws_vpc.keycloak.id
}
An AWS Load Balancer (ALB — Application Load Balancer) distributes incoming traffic to multiple ECS tasks (Keycloak containers) running in different availability zones.
- High availability (distributes traffic across multiple containers)
- Scalability (handles increasing users without crashing)
- Security (hides internal resources and prevents direct access)
AWS Application Load Balancer (ALB)
resource "aws_lb" "keycloak_alb" {
name = "keycloak-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.ecs_sg.id]
subnets = aws_subnet.private[*].id
}
1. name = "keycloak-alb"
- Defines the name of the Load Balancer as
"keycloak-alb"
.
2. internal = false
- The ALB is publicly accessible (needed for external users).
- If set to
true
, only internal services can access it.
3. load_balancer_type = "application"
- Specifies this is an Application Load Balancer (ALB), optimized for routing HTTP and HTTPS traffic.
4. security_groups = [aws_security_group.ecs_sg.id]
- The security group controls which traffic is allowed.
5. subnets = aws_subnet.private[*].id
- The ALB is deployed in private subnets (should be public subnets if external users need access).
Target Group (Attaches ECS Containers to ALB)
resource "aws_lb_target_group" "keycloak_tg" {
name = "keycloak-tg"
port = 8443
protocol = "HTTPS"
vpc_id = aws_vpc.keycloak.id
}
1. name = "keycloak-tg"
- Names the Target Group
"keycloak-tg"
(used by ECS service).
2. port = 8443
& protocol = "HTTPS"
- The ALB will listen on port 8443 and route HTTPS traffic.
3. vpc_id = aws_vpc.keycloak.id
- The Target Group is attached to the Keycloak VPC.
Step 9: Deploy and Test
- Run terraform apply to provision the infrastructure.
- Verify the ECS Service in AWS Console.
- Check the ALB URL for Keycloak Login.
Sample Docker file content is
FROM quay.io/keycloak/keycloak:26.0.6 AS builder
WORKDIR /opt/keycloak
ARG STOREPASS
RUN /opt/keycloak/bin/kc.sh build --health-enabled true \
--metrics-enabled true \
--db postgres \
--features preview \
--metrics-enabled=true
RUN keytool -genkeypair -storepass password \
-storetype PKCS12 -keyalg RSA \
-keysize 2048 -dname "CN=server" \
-alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" \
-keystore /opt/keycloak/conf/server.keystore
FROM quay.io/keycloak/keycloak:26.0.6
USER root
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
USER keycloak
COPY --from=builder /opt/keycloak/ /opt/keycloak/
COPY cache-ispn-jdbc-ping.xml /opt/keycloak/conf/cache-ispn-jdbc-ping.xml
ENTRYPOINT ["/docker-entrypoint.sh"]
docker-entrypoint.sh is
#!/bin/sh
set -e
exec /opt/keycloak/bin/kc.sh start \
--optimized \
--proxy-headers=xforwarded \
--http-metrics-slos=true \
"$@"
In addition you can set up AWS ECR, and publish your docker there
If you want to integration Keycloak into your project and don’t know where to start — you can reach me in telegram https://t.me/r137y or here https://dorokhovich.com/
You can find cache-ispn-jdbc-ping.xml that is used for JDBC PING setup here — https://gist.github.com/metronom72/8d581e94904613e1d1eb0edca5eb96f8