Skip to content

Unreal Horde

Unreal Engine Horde is a set of services supporting workflows Epic uses to develop Fortnite, Unreal Engine, and other titles. This module deploys the Unreal Engine Horde server on AWS Elastic Container Service using the image available from the Epic Games Github organization (requires Epic Games organization membership). Unreal Engine Horde relies on a Redis cache and a MongoDB compatible database. This module provides these services by provisioning an Amazon Elasticache with Redis OSS Compatibility cluster and an Amazon DocumentDB cluster.

Check out this video from Unreal Fest 2024 to learn more about the Unreal Horde module:

Watch the video

Deployment Architecture

Unreal Engine Horde Module Architecture

Prerequisites

Unreal Engine Horde is only available through the Epic Games Github organization's package registry or the Unreal Engine source code. In order to get access to this software you will need to join the Epic Games organization on Github and accept the Unreal Engine EULA.

Examples

For example configurations, please see the examples.

Server Configuration Delivery

The Horde server reads its startup settings from /app/Data/server.json. An init container (unreal-horde-init) renders this file before the server starts, so the settings the current Horde image honors (config path, Perforce connection) are delivered as configuration files rather than environment variables.

Perforce connection

To connect Horde to a Perforce server, set p4_port and provide the credentials through AWS Secrets Manager:

  1. Create a Secrets Manager secret containing a JSON object with the Perforce username and password Horde should connect as:

    {"username": "horde-service-user", "password": "example-password"}
    
  2. Pass the secret's ARN to the module via p4_credentials_secret_arn.

The credentials are injected into the init container at startup using ECS-native Secrets Manager integration (the task execution role is granted secretsmanager:GetSecretValue on the secret automatically). The init container substitutes them in-memory — they never appear in the ECS task definition, CloudTrail, container logs, or Terraform state.

The init container substitutes the credentials into two places, using the __P4_USERNAME__ / __P4_PASSWORD__ placeholder tokens:

  • server.json (rendered by the module) under plugins.build.perforce — the Horde server's Perforce connection.
  • globals.json (supplied by you via config_globals_json) — see below.

Horde configuration (globals.json)

The config_path variable controls the Horde server's configPath setting:

  • Set config_path = "globals.json" together with config_globals_json (a JSON string) to have the init container write your Horde configuration to /app/Data/globals.json.
  • Set config_path to a Perforce depot path (e.g. "//UE/Main/Config/globals.json") to have Horde load its configuration from your depot instead.

Perforce cluster credentials in globals.json

Horde 5.5 resolves the Perforce cluster used for stream polling from the top-level perforceClusters array in globals.json, and it does not fall back to server.json for that path. If your perforceClusters entry omits credentials, stream polling authenticates as the wrong user (or the OS default) and fails.

To deliver the Perforce credentials to a cluster without ever placing the password in your config string or Terraform state, put the same placeholder tokens inside a cluster's credentials entry. The init container substitutes them from p4_credentials_secret_arn at startup:

"perforceClusters": [
  {
    "name": "default",
    "serviceAccount": "svc-horde",
    "servers": [ { "serverAndPort": "ssl:perforce.example.com:1666" } ],
    "credentials": [
      { "userName": "__P4_USERNAME__", "password": "__P4_PASSWORD__" }
    ]
  }
]

The credentials substitution is a no-op when the placeholders are absent, so existing config_globals_json values are unaffected. For deploy diagnostics the init container prints globals.json to the [INIT] CloudWatch log before substituting the password, so __P4_PASSWORD__ appears as a literal placeholder in the log and the injected secret is never written to CloudWatch.

Requirements

Name Version
terraform >= 1.0
aws ~> 6.6
random ~> 3.7

Providers

Name Version
aws ~> 6.6
random ~> 3.7

Modules

No modules.

Resources

Name Type
aws_autoscaling_group.unreal_horde_agent_asg resource
aws_cloudwatch_log_group.unreal_horde_log_group resource
aws_docdb_cluster.horde resource
aws_docdb_cluster_instance.horde resource
aws_docdb_cluster_parameter_group.horde resource
aws_docdb_subnet_group.horde resource
aws_ecs_cluster.unreal_horde_cluster resource
aws_ecs_service.unreal_horde resource
aws_ecs_task_definition.unreal_horde_task_definition resource
aws_elasticache_cluster.horde resource
aws_elasticache_replication_group.horde resource
aws_elasticache_subnet_group.horde resource
aws_iam_instance_profile.unreal_horde_agent_instance_profile resource
aws_iam_policy.horde_agents_ec2_policy resource
aws_iam_policy.horde_agents_s3_policy resource
aws_iam_policy.unreal_horde_default_policy resource
aws_iam_policy.unreal_horde_elasticache_policy resource
aws_iam_policy.unreal_horde_recycle_policy resource
aws_iam_policy.unreal_horde_secrets_manager_policy resource
aws_iam_role.unreal_horde_agent_default_role resource
aws_iam_role.unreal_horde_default_role resource
aws_iam_role.unreal_horde_task_execution_role resource
aws_iam_role_policy_attachment.unreal_horde_agent_policy_attachments resource
aws_iam_role_policy_attachment.unreal_horde_agents_ec2_policy resource
aws_iam_role_policy_attachment.unreal_horde_agents_s3_policy resource
aws_iam_role_policy_attachment.unreal_horde_default_policy_attachment resource
aws_iam_role_policy_attachment.unreal_horde_elasticache_policy_attachment resource
aws_iam_role_policy_attachment.unreal_horde_recycle_attachment resource
aws_iam_role_policy_attachment.unreal_horde_secrets_manager_policy_attachment resource
aws_iam_role_policy_attachment.unreal_horde_task_execution_policy_attachment resource
aws_launch_template.unreal_horde_agent_template resource
aws_lb.unreal_horde_external_alb resource
aws_lb.unreal_horde_internal_alb resource
aws_lb_listener.unreal_horde_external_alb_http_listener resource
aws_lb_listener.unreal_horde_external_alb_https_listener resource
aws_lb_listener.unreal_horde_internal_alb_http_listener resource
aws_lb_listener.unreal_horde_internal_alb_https_listener resource
aws_lb_listener_rule.unreal_horde_external_alb_grpc_rule resource
aws_lb_listener_rule.unreal_horde_internal_alb_grpc_rule resource
aws_lb_target_group.unreal_horde_api_target_group_external resource
aws_lb_target_group.unreal_horde_api_target_group_internal resource
aws_lb_target_group.unreal_horde_grpc_target_group_external resource
aws_lb_target_group.unreal_horde_grpc_target_group_internal resource
aws_s3_bucket.ansible_playbooks resource
aws_s3_bucket.unreal_horde_alb_access_logs_bucket resource
aws_s3_bucket_lifecycle_configuration.access_logs_bucket_lifecycle_configuration resource
aws_s3_bucket_policy.alb_access_logs_bucket_policy resource
aws_s3_bucket_public_access_block.access_logs_bucket_public_block resource
aws_s3_bucket_public_access_block.ansible_playbooks_bucket_public_block resource
aws_s3_bucket_versioning.ansible_playbooks_versioning resource
aws_s3_object.unreal_horde_agent_playbook resource
aws_s3_object.unreal_horde_agent_service resource
aws_security_group.unreal_horde_agent_sg resource
aws_security_group.unreal_horde_docdb_sg resource
aws_security_group.unreal_horde_elasticache_sg resource
aws_security_group.unreal_horde_external_alb_sg resource
aws_security_group.unreal_horde_internal_alb_sg resource
aws_security_group.unreal_horde_sg resource
aws_ssm_association.configure_unreal_horde_agent resource
aws_ssm_document.ansible_run_document resource
aws_vpc_security_group_egress_rule.unreal_horde_agents_outbound_ipv4 resource
aws_vpc_security_group_egress_rule.unreal_horde_agents_outbound_ipv6 resource
aws_vpc_security_group_egress_rule.unreal_horde_external_alb_outbound_service_api resource
aws_vpc_security_group_egress_rule.unreal_horde_external_alb_outbound_service_grpc resource
aws_vpc_security_group_egress_rule.unreal_horde_internal_alb_outbound_service_api resource
aws_vpc_security_group_egress_rule.unreal_horde_internal_alb_outbound_service_grpc resource
aws_vpc_security_group_egress_rule.unreal_horde_outbound_ipv4 resource
aws_vpc_security_group_egress_rule.unreal_horde_outbound_ipv6 resource
aws_vpc_security_group_ingress_rule.unreal_horde_agents_inbound_agents resource
aws_vpc_security_group_ingress_rule.unreal_horde_docdb_ingress resource
aws_vpc_security_group_ingress_rule.unreal_horde_elasticache_ingress resource
aws_vpc_security_group_ingress_rule.unreal_horde_inbound_external_alb_api resource
aws_vpc_security_group_ingress_rule.unreal_horde_inbound_external_alb_grpc resource
aws_vpc_security_group_ingress_rule.unreal_horde_inbound_internal_alb_api resource
aws_vpc_security_group_ingress_rule.unreal_horde_inbound_internal_alb_grpc resource
aws_vpc_security_group_ingress_rule.unreal_horde_service_inbound_agents resource
aws_vpc_security_group_ingress_rule.unreal_horde_service_inbound_containers resource
random_string.unreal_horde resource
random_string.unreal_horde_alb_access_logs_bucket_suffix resource
random_string.unreal_horde_ansible_playbooks_bucket_suffix resource
aws_ami.unreal_horde_agent_ami data source
aws_ecs_cluster.unreal_horde_cluster data source
aws_elb_service_account.main data source
aws_iam_policy_document.access_logs_bucket_alb_write data source
aws_iam_policy_document.ec2_trust_relationship data source
aws_iam_policy_document.ecs_tasks_trust_relationship data source
aws_iam_policy_document.horde_agents_ec2_policy data source
aws_iam_policy_document.horde_agents_s3_policy data source
aws_iam_policy_document.unreal_horde_default_policy data source
aws_iam_policy_document.unreal_horde_elasticache_policy data source
aws_iam_policy_document.unreal_horde_recycle_policy data source
aws_iam_policy_document.unreal_horde_secrets_manager_policy data source
aws_region.current data source

Inputs

Name Description Type Default Required
certificate_arn The TLS certificate ARN for the Unreal Horde load balancer. string n/a yes
fully_qualified_domain_name The fully qualified domain name where your Unreal Engine Horde server will be available. This agents will use this to enroll. string n/a yes
unreal_horde_service_subnets A list of subnets to deploy the Unreal Horde service into. Private subnets are recommended. list(string) n/a yes
vpc_id The ID of the existing VPC you would like to deploy Unreal Horde into. string n/a yes
admin_claim_type The claim type for administrators. string null no
admin_claim_value The claim value for administrators. string null no
agent_dotnet_runtime_version The dotnet-runtime-{} package to install (see your engine version's release notes for supported version) string "6.0" no
agent_enable_long_paths Enable NTFS long-path support (LongPathsEnabled=1) on Windows agents. Recommended for from-source Unreal Engine builds, which routinely exceed MAX_PATH. bool false no
agent_uba_compute_ports Inbound TCP port range to open in the host Windows Firewall for UBA (Unreal Build Accelerator) distributed compile workers, e.g. "7000-7010". The agent security group must also allow this range. Set to null to skip the firewall rule. string null no
agent_uba_horde_pool If set, writes a UBT BuildConfiguration.xml on Windows agents that enables UBA-over-Horde, targeting this Horde pool name (must match a pool in your globals.json, e.g. "Win-UE5"). Leave null to not configure UBA-over-Horde. string null no
agent_uba_max_workers MaxWorkers value for the UBT BuildConfiguration.xml block (only used when agent_uba_horde_pool is set). number 4 no
agent_working_dir Working directory for the Horde agent on Windows. A SHORT path (e.g. "C:\H") keeps deeply-nested engine-plugin response-file paths under MAX_PATH for link.exe/cl.exe. Written to the durable agent.json so it survives agent self-upgrades. Set to null to leave the agent default. string null no
agents Configures autoscaling groups to be used as build agents by Unreal Engine Horde.
map(object({
ami = string
instance_type = string
horde_pool_name = optional(string)
create_asg = optional(bool, true)
block_device_mappings = list(
object({
device_name = string
ebs = object({
volume_size = number
})
})
)
min_size = optional(number, 0)
max_size = optional(number, 1)
}))
{} no
auth_method The authentication method for the Horde server. string null no
cluster_name The name of the cluster to deploy the Unreal Horde into. Defaults to null and a cluster will be created. string null no
config_globals_json JSON string content for the Horde globals.json configuration file. When non-empty it is written to /app/Data/globals.json by the init container; pair it with config_path = "globals.json". The init container substitutes the __P4_USERNAME__ / __P4_PASSWORD__ placeholder tokens (from p4_credentials_secret_arn) into this content at startup, so you can place them inside a perforceClusters[].credentials entry without the password ever landing in Terraform state or the task definition. Substitution is a no-op when the placeholders are absent. Leave empty to manage config another way (e.g. a Perforce config_path). string "" no
config_path Value for the Horde server's configPath setting (written to /app/Data/server.json). Use "globals.json" to load the file rendered from config_globals_json into /app/Data, or a Perforce path (e.g. "//UE/Main/...") to load config from the depot. string null no
container_api_port The container port for the Unreal Horde web server. number 5000 no
container_cpu The CPU allotment for the Unreal Horde container. number 1024 no
container_grpc_port The container port for the Unreal Horde GRPC channel. number 5002 no
container_memory The memory allotment for the Unreal Horde container. number 4096 no
container_name The name of the Unreal Horde container. string "unreal-horde-container" no
create_external_alb Set this flag to true to create an external load balancer for Unreal Horde. bool true no
create_internal_alb Set this flag to true to create an internal load balancer for Unreal Horde. bool true no
create_unreal_horde_default_policy Optional creation of Unreal Horde default IAM Policy. Default is set to true. bool true no
create_unreal_horde_default_role Optional creation of Unreal Horde default IAM Role. Default is set to true. bool true no
create_unreal_horde_recycle_policy Optional creation of Unreal Horde IAM Policy allowing usage of the AwsReuse/AwsRecycle fleet manager. bool false no
custom_cache_connection_config The redis-compatible connection configuration that Horde should use. string null no
custom_unreal_horde_role ARN of the custom IAM Role you wish to use with Unreal Horde. string null no
database_connection_string The database connection string that Horde should use. string null no
debug Set this flag to enable ECS execute permissions on the Unreal Horde container and force new service deployments on Terraform apply. bool false no
desired_container_count The desired number of containers running Unreal Horde. number 1 no
docdb_backup_retention_period Number of days to retain backups for DocumentDB cluster. number 7 no
docdb_instance_class The instance class for the Horde DocumentDB cluster. string "db.t4g.medium" no
docdb_instance_count The number of instances to provision for the Horde DocumentDB cluster. number 2 no
docdb_master_password Master password created for DocumentDB cluster. string "mustbeeightchars" no
docdb_master_username Master username created for DocumentDB cluster. string "horde" no
docdb_preferred_backup_window The preferred window for DocumentDB backups to be created. string "07:00-09:00" no
docdb_skip_final_snapshot Flag for whether a final snapshot should be created when the cluster is destroyed. bool true no
docdb_storage_encrypted Configure DocumentDB storage at rest. bool true no
elasticache_cluster_count Number of cache cluster to provision in the Elasticache cluster. number 2 no
elasticache_engine The engine to use for ElastiCache (redis or valkey) string "redis" no
elasticache_node_count Number of cache nodes to provision in the Elasticache cluster. number 1 no
elasticache_node_type The type of nodes provisioned in the Elasticache cluster. string "cache.t4g.micro" no
elasticache_port The port for the ElastiCache cluster. number 6379 no
elasticache_redis_engine_version The version of the Redis engine to use. string "7.0" no
elasticache_redis_parameter_group_name The name of the Redis parameter group to use. string "default.redis7" no
elasticache_snapshot_retention_limit The number of Elasticache snapshots to retain. number 5 no
elasticache_valkey_engine_version The version of the ElastiCache engine to use. string "7.2" no
elasticache_valkey_parameter_group_name The name of the Valkey parameter group to use. string "default.valkey7" no
enable_new_agents_by_default Set this flag to automatically enable new agents that enroll with the Horde Server. bool false no
enable_unreal_horde_alb_access_logs Enables access logging for the Unreal Horde ALB. Defaults to true. bool true no
enable_unreal_horde_alb_deletion_protection Enables deletion protection for the Unreal Horde ALB. Defaults to true. bool false no
environment The current environment (e.g. Development, Staging, Production, etc.). This will tag ressources and set ASPNETCORE_ENVIRONMENT variable. string "Development" no
existing_security_groups A list of existing security group IDs to attach to the Unreal Horde load balancer. list(string) [] no
github_credentials_secret_arn A secret containing the Github username and password with permissions to the EpicGames organization. string null no
image The Horde Server image to use in the ECS service. string "ghcr.io/epicgames/horde-server:latest-bundled" no
name The name attached to Unreal Engine Horde module resources. string "unreal-horde" no
oidc_audience The audience used for validating externally issued tokens. string null no
oidc_authority The authority for the OIDC authentication provider used. string null no
oidc_client_id The client ID used for authenticating with the OIDC provider. string null no
oidc_client_secret The client secret used for authenticating with the OIDC provider. string null no
oidc_signin_redirect The sign-in redirect URL for the OIDC provider. string null no
p4_credentials_secret_arn ARN of an AWS Secrets Manager secret containing the Perforce credentials the Horde server connects with, as a JSON object: {"username": "...", "password": "..."}. The credentials are fetched at container startup and injected into /app/Data/server.json under plugins.build.perforce (and into any __P4_USERNAME__/__P4_PASSWORD__ placeholders in config_globals_json) - they never appear in the ECS task definition or Terraform state. Required when p4_port is set. string null no
p4_port The Perforce server to connect to. string null no
project_prefix The project prefix for this workload. This is appeneded to the beginning of most resource names. string "cgd" no
tags Tags to apply to resources. map(any)
{
"iac-management": "CGD-Toolkit",
"iac-module": "unreal-horde",
"iac-provider": "Terraform"
}
no
unreal_horde_alb_access_logs_bucket ID of the S3 bucket for Unreal Horde ALB access log storage. If access logging is enabled and this is null the module creates a bucket. string null no
unreal_horde_alb_access_logs_prefix Log prefix for Unreal Horde ALB access logs. If null the project prefix and module name are used. string null no
unreal_horde_cloudwatch_log_retention_in_days The log retention in days of the cloudwatch log group for Unreal Horde. string 365 no
unreal_horde_external_alb_subnets A list of subnets to deploy the Unreal Horde load balancer into. Public subnets are recommended. list(string) [] no
unreal_horde_internal_alb_subnets A list of subnets to deploy the Unreal Horde internal load balancer into. Private subnets are recommended. list(string) [] no

Outputs

Name Description
agent_security_group_id n/a
external_alb_dns_name n/a
external_alb_sg_id n/a
external_alb_zone_id n/a
internal_alb_dns_name n/a
internal_alb_sg_id n/a
internal_alb_zone_id n/a
service_security_group_id n/a