Katta brings zero-config storage management and zero-knowledge key management for teams and organizations.
Set up Katta Hub in a custom AWS hosted zone.
-
Register a domain
<dns_suffix>such asexample.netin AWS Route53 to be used. A subdomainhub.katta.example.netandkeycloak.katta.example.netwill be created for the Katta deployment when applying Terraform. -
Install Docker.
-
Setup AWS CLI and configure credentials in environment:
export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= export AWS_SESSION_TOKEN= export AWS_DEFAULT_REGION= export AWS_USE_DUALSTACK_ENDPOINT=false
-
Setup Terraform Workspace
terraform workspace new katta
The workspace name determines the infix
<workspace>in the subdomains created:https://hub.<workspace>.<dns_suffix>https://keycloak.<workspace>.<dns_suffix>
-
Override default Terraform configuration
Defaults can be found in
terraform.tfvars.template. Either copy toterraform.tfvars(not under version control) or overridden by environment variables:export TF_VAR_region=$AWS_DEFAULT_REGION export TF_VAR_dns_suffix=example.net export TF_VAR_keycloak_db_password= export TF_VAR_keycloak_admin_password= export TF_VAR_hub_db_password= export TF_VAR_hub_keycloak_system_client_secret=top-secret export TF_VAR_hub_keycloak_oidc_cryptomator_vaults_client_secret=top-secret
-
Add hosted zone for domain in AWS Route53 if missing:
aws route53 create-hosted-zone --name $TF_VAR_dns_suffix --caller-reference $(date +%s)
Warning: For domain validation to work in AWS Certificate Manager, you must ensure the name servers set in the hosted zone match the name servers set in the Route53 domain registration.
-
Add GitHub Personal Access Token
- AWS ECR pull-through cache requires authentication even for public GitHub Container Registry repositories.
- Create a GitHub Personal Access Token with
read:packagespermission usingghCLI:
# Create a new token specifically for this: gh auth login --scopes read:packages- Then add the token to environment:
export TF_VAR_github_token=$(gh auth token)
Alternatively, create manually via GitHub web UI:
- Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Generate new token with
read:packagesscope - Add to
terraform.tfvars:github_token = "ghp_your_token_here"
-
Validate environment
terraform init terraform validate terraform plan
-
Deploy environment
terraform apply --auto-approve
-
Log in via Web Browser
- Open
https://hub.<workspace>.<dns_suffix>in browser to log in to Katta Hub with the default credentials withadminrole. - Open
https://keycloak.<workspace>.<dns_suffix>in browser to log in to Katta Keycloak with the admin user (defaultkeycloak_admin) and password as$TF_VAR_keycloak_admin_password.
Preconditions from leftovers previous run: Rename secret names, they have a minimum grace period of 7 days before deletion.
- Destroy environment
terraform destroy --auto-approve
Container images are automatically pulled from GitHub Container Registry (ghcr.io) via Amazon ECR pull-through cache rules. This eliminates the need to manually pull and push images to ECR.
- Keycloak:
ghcr.io/cryptomator/keycloak:26.4.5 - Katta Hub:
ghcr.io/shift7-ch/katta-server:982baf0-amd64
Images are cached in ECR with the prefix <workspace>-ghcr/ and pulled automatically when ECS tasks start.
- no URL paths
/kcfor Keycloak and/<realm>/for hub instances - non-shared Keycloak
- default realm
aws_acm_certificate_validation.keycloak_cert_validation: Still creating... [4m41s elapsed]
aws_acm_certificate_validation.hub_cert_validation: Still creating... [4m40s elapsed]
Ensure the name server entries for the hosted zone match the name servers in the domain registration in Route 53.
aws logs tail `terraform workspace show`-hub-log-group --output text --since 30s --follow
aws logs tail `terraform workspace show`-keycloak-log-group --output text --since 30s --followEnable execute command with variable ecs_enable_execute_command.
# TF_VAR_region
WORKSPACE=$(terraform workspace show)
# inspect services
aws ecs describe-services --region ${TF_VAR_region} --cluster ${WORKSPACE}-cluster --service ${WORKSPACE}-keycloak-ecs-service
aws ecs describe-services --region ${TF_VAR_region} --cluster ${WORKSPACE}-cluster --service ${WORKSPACE}-hub-ecs-service
# list task
aws ecs list-tasks --region ${TF_VAR_region} --cluster ${WORKSPACE}-cluster
#{
# "taskArns": [
# "arn:aws:ecs:<region>:<account ID>:task/che-cluster/8bd3030e916a47e08cd579bc70009737",
# "arn:aws:ecs:<region>:<account ID>:task/che-cluster/fb33d05b480f4cc19cb4299190b916d5"
# ]
#}
# inspect tasks
aws ecs describe-tasks --region ${TF_VAR_region} --cluster ${WORKSPACE}-cluster --task 8bd3030e916a47e08cd579bc70009737
aws ecs describe-tasks --region ${TF_VAR_region} --cluster ${WORKSPACE}-cluster --task fb33d05b480f4cc19cb4299190b916d5
# debug containers:
aws ecs execute-command \
--region ${TF_VAR_region} \
--cluster ${WORKSPACE}-cluster \
--task fb33d05b480f4cc19cb4299190b916d5 \
--container ${WORKSPACE}-container-keycloak \
--interactive \
--command "/bin/sh"
aws ecs execute-command \
--region ${TF_VAR_region} \
--cluster ${WORKSPACE}-cluster \
--task 8bd3030e916a47e08cd579bc70009737 \
--container ${WORKSPACE}-container-hub \
--interactive \
--command "/bin/sh"Initial terraform script from source.
Generated with Terramaid:
terramaid run -s "Overview" --exclude-types "aws_iam_*" --exclude-types "aws_security_group" --exclude-types "aws_secretsmanager_*" --exclude-types "aws_appautoscaling_*" --exclude-types "aws_cloudwatch_*" --exclude-types "aws_acm*" --exclude-types "null*" --exclude-types "aws_route53*"
terramaid run -s "Security Groups" --include-types "aws_security_group" --include-types "aws_db_instance" --include-types "aws_ecs_*" --include-types "aws_lb" --include-types "aws_vpc_endpoint" --include-types "aws_ecs_cluster" --include-types "aws_vpc"flowchart TD
subgraph Overview
data_aws_availability_zones_available["data.aws_availability_zones.available"]
data_aws_caller_identity_current["data.aws_caller_identity.current"]
data_local_file_cryptomator_realm["data.local_file.cryptomator_realm"]
aws_db_instance_hub_db["aws_db_instance.hub_db"]
aws_db_instance_postgres["aws_db_instance.postgres"]
aws_db_subnet_group_private_subnet_group["aws_db_subnet_group.private_subnet_group"]
aws_ecr_pull_through_cache_rule_github["aws_ecr_pull_through_cache_rule.github"]
aws_ecs_cluster_katta_ecs_cluster["aws_ecs_cluster.katta_ecs_cluster"]
aws_ecs_service_katta_server_ecs_service["aws_ecs_service.katta_server_ecs_service"]
aws_ecs_service_keycloak_ecs_service["aws_ecs_service.keycloak_ecs_service"]
aws_ecs_task_definition_katta_server_ecs_task["aws_ecs_task_definition.katta_server_ecs_task"]
aws_ecs_task_definition_keycloak_ecs_task["aws_ecs_task_definition.keycloak_ecs_task"]
aws_eip_nat["aws_eip.nat"]
aws_internet_gateway_public_igw["aws_internet_gateway.public_igw"]
aws_lb_hub_public_alb["aws_lb.hub_public_alb"]
aws_lb_keycloak_public_alb["aws_lb.keycloak_public_alb"]
aws_lb_listener_hub_http_listener["aws_lb_listener.hub_http_listener"]
aws_lb_listener_hub_https_listener["aws_lb_listener.hub_https_listener"]
aws_lb_listener_keycloak_http_listener["aws_lb_listener.keycloak_http_listener"]
aws_lb_listener_keycloak_https_listener["aws_lb_listener.keycloak_https_listener"]
aws_lb_target_group_hub_ecs_target_group["aws_lb_target_group.hub_ecs_target_group"]
aws_lb_target_group_keycloak_ecs_target_group["aws_lb_target_group.keycloak_ecs_target_group"]
aws_nat_gateway_private_nat["aws_nat_gateway.private_nat"]
aws_route_internet_access["aws_route.internet_access"]
aws_route_table_private_subnet["aws_route_table.private_subnet"]
aws_route_table_public_subnet["aws_route_table.public_subnet"]
aws_route_table_association_private_subnets["aws_route_table_association.private_subnets"]
aws_route_table_association_public_subnets["aws_route_table_association.public_subnets"]
aws_service_discovery_http_namespace_katta_service_connect["aws_service_discovery_http_namespace.katta_service_connect"]
aws_subnet_private["aws_subnet.private"]
aws_subnet_public["aws_subnet.public"]
aws_vpc_katta["aws_vpc.katta"]
aws_vpc_endpoint_ecr_api["aws_vpc_endpoint.ecr_api"]
aws_vpc_endpoint_ecr_dkr["aws_vpc_endpoint.ecr_dkr"]
aws_vpc_endpoint_ecr_logs["aws_vpc_endpoint.ecr_logs"]
aws_vpc_endpoint_ecr_secretsmanager["aws_vpc_endpoint.ecr_secretsmanager"]
random_id_secret_suffix["random_id.secret_suffix"]
end
aws_db_instance_hub_db --> aws_db_subnet_group_private_subnet_group
aws_db_instance_postgres --> aws_db_subnet_group_private_subnet_group
aws_db_subnet_group_private_subnet_group --> aws_subnet_private
aws_ecs_cluster_katta_ecs_cluster --> aws_service_discovery_http_namespace_katta_service_connect
aws_ecs_service_katta_server_ecs_service --> aws_ecs_task_definition_katta_server_ecs_task
aws_ecs_service_katta_server_ecs_service --> aws_lb_target_group_hub_ecs_target_group
aws_ecs_service_keycloak_ecs_service --> aws_ecs_cluster_katta_ecs_cluster
aws_ecs_service_keycloak_ecs_service --> aws_ecs_task_definition_keycloak_ecs_task
aws_ecs_service_keycloak_ecs_service --> aws_lb_target_group_keycloak_ecs_target_group
aws_ecs_task_definition_katta_server_ecs_task --> aws_db_instance_hub_db
aws_ecs_task_definition_katta_server_ecs_task --> aws_ecs_service_keycloak_ecs_service
aws_ecs_task_definition_keycloak_ecs_task --> data_local_file_cryptomator_realm
aws_ecs_task_definition_keycloak_ecs_task --> aws_db_instance_postgres
aws_internet_gateway_public_igw --> aws_vpc_katta
aws_lb_hub_public_alb --> aws_subnet_public
aws_lb_keycloak_public_alb --> aws_subnet_public
aws_lb_listener_hub_http_listener --> aws_lb_hub_public_alb
aws_lb_listener_hub_https_listener --> aws_lb_hub_public_alb
aws_lb_listener_hub_https_listener --> aws_lb_target_group_hub_ecs_target_group
aws_lb_listener_keycloak_http_listener --> aws_lb_keycloak_public_alb
aws_lb_listener_keycloak_https_listener --> aws_lb_keycloak_public_alb
aws_lb_listener_keycloak_https_listener --> aws_lb_target_group_keycloak_ecs_target_group
aws_lb_target_group_hub_ecs_target_group --> aws_vpc_katta
aws_lb_target_group_keycloak_ecs_target_group --> aws_vpc_katta
aws_nat_gateway_private_nat --> aws_eip_nat
aws_nat_gateway_private_nat --> aws_subnet_public
aws_route_internet_access --> aws_internet_gateway_public_igw
aws_route_internet_access --> aws_route_table_public_subnet
aws_route_table_private_subnet --> aws_nat_gateway_private_nat
aws_route_table_public_subnet --> aws_vpc_katta
aws_route_table_association_private_subnets --> aws_route_table_private_subnet
aws_route_table_association_private_subnets --> aws_subnet_private
aws_route_table_association_public_subnets --> aws_route_table_public_subnet
aws_route_table_association_public_subnets --> aws_subnet_public
aws_subnet_private --> data_aws_availability_zones_available
aws_subnet_private --> aws_vpc_katta
aws_subnet_public --> data_aws_availability_zones_available
aws_subnet_public --> aws_vpc_katta
aws_vpc_endpoint_ecr_api --> aws_subnet_private
aws_vpc_endpoint_ecr_dkr --> aws_subnet_private
aws_vpc_endpoint_ecr_logs --> aws_subnet_private
aws_vpc_endpoint_ecr_secretsmanager --> aws_subnet_private
flowchart TD
subgraph Security Groups
aws_db_instance_hub_db["aws_db_instance.hub_db"]
aws_db_instance_postgres["aws_db_instance.postgres"]
aws_ecs_cluster_katta_ecs_cluster["aws_ecs_cluster.katta_ecs_cluster"]
aws_ecs_service_katta_server_ecs_service["aws_ecs_service.katta_server_ecs_service"]
aws_ecs_service_keycloak_ecs_service["aws_ecs_service.keycloak_ecs_service"]
aws_ecs_task_definition_katta_server_ecs_task["aws_ecs_task_definition.katta_server_ecs_task"]
aws_ecs_task_definition_keycloak_ecs_task["aws_ecs_task_definition.keycloak_ecs_task"]
aws_lb_hub_public_alb["aws_lb.hub_public_alb"]
aws_lb_keycloak_public_alb["aws_lb.keycloak_public_alb"]
aws_security_group_alb_sg["aws_security_group.alb_sg"]
aws_security_group_ecs_cluster_sg["aws_security_group.ecs_cluster_sg"]
aws_security_group_rds_sg["aws_security_group.rds_sg"]
aws_security_group_vpc_endpoint_sg["aws_security_group.vpc_endpoint_sg"]
aws_vpc_katta["aws_vpc.katta"]
aws_vpc_endpoint_ecr_api["aws_vpc_endpoint.ecr_api"]
aws_vpc_endpoint_ecr_dkr["aws_vpc_endpoint.ecr_dkr"]
aws_vpc_endpoint_ecr_logs["aws_vpc_endpoint.ecr_logs"]
aws_vpc_endpoint_ecr_secretsmanager["aws_vpc_endpoint.ecr_secretsmanager"]
end
aws_db_instance_hub_db --> aws_security_group_rds_sg
aws_db_instance_postgres --> aws_security_group_rds_sg
aws_ecs_service_katta_server_ecs_service --> aws_ecs_task_definition_katta_server_ecs_task
aws_ecs_service_keycloak_ecs_service --> aws_ecs_cluster_katta_ecs_cluster
aws_ecs_service_keycloak_ecs_service --> aws_ecs_task_definition_keycloak_ecs_task
aws_ecs_task_definition_katta_server_ecs_task --> aws_db_instance_hub_db
aws_ecs_task_definition_katta_server_ecs_task --> aws_ecs_service_keycloak_ecs_service
aws_ecs_task_definition_keycloak_ecs_task --> aws_db_instance_postgres
aws_lb_hub_public_alb --> aws_security_group_alb_sg
aws_lb_keycloak_public_alb --> aws_security_group_alb_sg
aws_security_group_alb_sg --> aws_vpc_katta
aws_security_group_ecs_cluster_sg --> aws_security_group_alb_sg
aws_security_group_rds_sg --> aws_security_group_ecs_cluster_sg
aws_security_group_vpc_endpoint_sg --> aws_security_group_ecs_cluster_sg
aws_vpc_endpoint_ecr_api --> aws_security_group_vpc_endpoint_sg
aws_vpc_endpoint_ecr_dkr --> aws_security_group_vpc_endpoint_sg
aws_vpc_endpoint_ecr_logs --> aws_security_group_vpc_endpoint_sg
aws_vpc_endpoint_ecr_secretsmanager --> aws_security_group_vpc_endpoint_sg