This checklist tells you exactly what to update to onboard a new Forge tenant.
Copy these templates and place them at the correct paths.
examples/templates/tenant/_global_settings/tenant.hclexamples/templates/tenant/tenant/terragrunt.hclexamples/templates/tenant/tenant/runner_settings.hclexamples/templates/tenant/tenant/config.yamlexamples/deployments/forge-tenant/terragrunt/_global_settings/tenants/<tenant_name>.hcl
examples/deployments/forge-tenant/terragrunt/environments/<aws_account>/regions/<aws_region>/vpcs/<vpc_alias>/tenants/<tenant_name>/terragrunt.hcl
examples/deployments/forge-tenant/terragrunt/environments/<aws_account>/regions/<aws_region>/vpcs/<vpc_alias>/tenants/<tenant_name>/runner_settings.hcl
examples/deployments/forge-tenant/terragrunt/environments/<aws_account>/regions/<aws_region>/vpcs/<vpc_alias>/tenants/<tenant_name>/config.yaml
sbg, account=sec-plat, region=eu-west-1, vpc_alias=sharedcp examples/templates/tenant/_global_settings/tenant.hcl \
examples/deployments/forge-tenant/terragrunt/_global_settings/tenants/sbg.hcl
mkdir -p examples/deployments/forge-tenant/terragrunt/environments/sec-plat/regions/eu-west-1/vpcs/shared/tenants/sbg
cp examples/templates/tenant/tenant/terragrunt.hcl \
examples/deployments/forge-tenant/terragrunt/environments/sec-plat/regions/eu-west-1/vpcs/shared/tenants/sbg/terragrunt.hcl
cp examples/templates/tenant/tenant/runner_settings.hcl \
examples/deployments/forge-tenant/terragrunt/environments/sec-plat/regions/eu-west-1/vpcs/shared/tenants/sbg/runner_settings.hcl
cp examples/templates/tenant/tenant/config.yaml \
examples/deployments/forge-tenant/terragrunt/environments/sec-plat/regions/eu-west-1/vpcs/shared/tenants/sbg/config.yaml
config.yaml — Tenant Configuration FieldsControls GitHub integration, IAM roles, runner specs (EC2 & ARC).
gh_config:
ghes_url: <GITHUB_URL> # Empty string for github.com, full GHES URL otherwise
ghes_org: <GITHUB_ORG> # Exact GitHub organization name
repository_selection: <repository_selection> # Type of repository selection (all or selected)
github_webhook_relay: # (Optional) Forward incoming GitHub webhook events cross-account via EventBridge
enabled: false # Set true to forward events to another account/region
destination_account_id: "" # Target AWS account ID owning the destination EventBridge bus
destination_event_bus_name: "" # Destination EventBridge bus name (blank => default bus when supported)
destination_region: "" # Destination AWS region for the forwarding rule
destination_reader_role_arn: "" # IAM role in destination allowed to read forwarded events (leave blank if not needed)
# NOTE: Leave all destination_* fields blank when enabled=false; they are ignored.
tenant:
iam_roles_to_assume: # List of full AWS IAM role ARNs runners may assume for workloads
- arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
ecr_registries: # Allowed ECR repo URLs (full), e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com
- <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com
github_logs_reader_role_arns: # (Optional) IAM role ARNs granted read (+ KMS decrypt) access to archived GitHub job/workflow logs
- arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
ec2_runner_specs:
<runner_type>: # e.g. small, medium, gpu
ami_name: <AMI_NAME_PATTERN> # AMI name pattern, supports wildcard *, e.g. forge-gh-runner-v*
ami_owner: <ACCOUNT_ID> # AWS account ID owning AMI
ami_kms_key_arn: '' # Set to '' if AMI is unencrypted, else KMS ARN string
max_instances: <MAX_PARALLEL> # Max EC2 runners allowed in parallel
instance_types: # List of allowed instance types (prefer spot-compatible)
- <AWS_INSTANCE_TYPE> # e.g. t3.large, m5.large
pool_config: # Warm pool config for pre-warming runners; empty list [] disables
- size: <POOL_SIZE> # Number of instances to keep warm
schedule_expression: <AWS_CRON_EXPR> # AWS cron expression (6 fields, use AWS docs)
schedule_expression_timezone: <TIMEZONE> # Optional timezone, e.g. UTC, America/New_York
arc_runner_specs:
<runner_type>: # e.g. dependabot, k8s
runner_size:
max_runners: <MAX> # Max pods/runners (Max Pod allowed in parallel)
min_runners: <MIN> # Min pods/runners (warm pool)
scale_set_name: <NAME> # Used for ARC annotations and scale set identification
scale_set_type: <dind|k8s> # Must be exactly 'dind' or 'k8s', no other values allowed
container_actions_runner: <ECR_IMAGE_URL> # Full ECR container image URL for the runner container
container_requests_cpu: <CPU> # Kubernetes CPU requests, e.g. 500m (mandatory unit)
container_requests_memory: <MEM> # Kubernetes memory requests, e.g. 1Gi (mandatory unit)
container_limits_cpu: <CPU> # Kubernetes CPU limits
container_limits_memory: <MEM> # Kubernetes memory limits
ghes_url: empty for github.com, full URL for GHES.iam_roles_to_assume: full ARNs only, no wildcards.ecr_registries: must be full URLs, including account and region.github_logs_reader_role_arns:
ami_kms_key_arn: must be explicitly set to '' if AMI not encrypted; otherwise runner fails.max_instances: check AWS EC2 quota before setting.instance_types: spot-compatible preferred for cost savings.pool_config.schedule_expression: AWS cron syntax with 6 fields, not standard cron. Example: cron(0 8 * * ? *). See AWS docs.scale_set_type: only dind or k8s. Wrong values cause runtime errors.500m, 1Gi). Missing units break pods.github_webhook_relay Guidanceenabled: true only when you need to forward GitHub webhook events (e.g., workflow_job) to a central or cross-account EventBridge bus.destination_account_id and usually destination_region.destination_event_bus_name: Leave blank to target the default bus; specify to use a custom bus.destination_reader_role_arn: Provide if a specific role in the destination account needs read access for diagnostics/metrics; leave blank otherwise.destination_* keys are ignored when enabled: false (can be left as placeholders).ami_kms_key_arn = '' when AMI isn’t encrypted → Terraform errors.config.yaml Examplegh_config:
ghes_url: ''
ghes_org: cisco-sbg
github_webhook_relay:
enabled: false
destination_account_id: ""
destination_event_bus_name: ""
destination_region: ""
destination_reader_role_arn: ""
tenant:
iam_roles_to_assume:
- arn:aws:iam::123456789012:role/role_for_forge_runners
ecr_registries:
- 123456789012.dkr.ecr.us-east-1.amazonaws.com
github_logs_reader_role_arns:
- arn:aws:iam::123456789012:role/github_logs_reader
ec2_runner_specs:
small:
ami_name: forge-gh-runner-v*
ami_owner: '123456789012'
ami_kms_key_arn: ''
max_instances: 10
instance_types:
- t3.small
- t3.medium
pool_config:
- size: 2
schedule_expression: "cron(*/10 8 * * ? *)"
schedule_expression_timezone: "America/Los_Angeles"
arc_runner_specs:
dependabot:
runner_size:
max_runners: 100
min_runners: 1
scale_set_name: dependabot
scale_set_type: dind
container_actions_runner: 123456789012.dkr.ecr.us-east-1.amazonaws.com/actions-runner:latest
container_requests_cpu: 500m
container_requests_memory: 1Gi
container_limits_cpu: '1'
container_limits_memory: 2Gi
cd examples/deployments/forge-tenant/terragrunt/environments/<aws_account_alias>/regions/<aws_region>/vpcs/<vpc_alias>/tenants/<tenant_name>
terragrunt apply --target aws_secretsmanager_secret_version.cicd_secrets
Pro tip: Use
--targetcarefully — only apply secrets here to avoid accidental resource changes in other modules.
docker pull ghcr.io/cisco-open/forge-forge-github-app-register:main
docker run --rm -p 5000:5000 ghcr.io/cisco-open/forge-forge-github-app-register:main
Go to http://localhost:5000/ in your browser.
${local.tenant_name}-${local.region_alias}-${local.vpc_alias}-${include.env.locals.runner_group_name_suffix}
Example:
sec-plat-euw1-shared-sbg-cicd-forge
Save the JSON file securely. The private key (pem) in it is your authentication backbone. Lose it, and you start over.
You need these values from the JSON (or GitHub later) to configure Forge’s secrets:
client_idid (App ID)installation_id (get it by installing the app on repos/org)pem (private key)Permissions must be exactly:
actions: readchecks: readmetadata: readorganization_self_hosted_runners: writeorganization_administration: writeSubscribe the app to "workflow_job" event — this is how your runners get triggered.
Don’t forget to install the GitHub App on the repositories or organizations that will use these runners.
Run the update-github-app-secrets.sh script to inject critical GitHub App values into your secrets:
./scripts/update-github-app-secrets.sh /full/path/to/tenant_dir client_id <GITHUB_APP_CLIENT_ID>
./scripts/update-github-app-secrets.sh /full/path/to/tenant_dir name <GITHUB_APP_NAME>
./scripts/update-github-app-secrets.sh /full/path/to/tenant_dir id <GITHUB_APP_ID>
./scripts/update-github-app-secrets.sh /full/path/to/tenant_dir key /path/to/private-key.pem
./scripts/update-github-app-secrets.sh /full/path/to/tenant_dir installation_id <GITHUB_APP_INSTALLATION_ID>
chmod 600) to avoid permission errors.terragrunt plan or AWS Console if you want to double-check.cd examples/deployments/forge-tenant/terragrunt/environments/<aws_account_alias>/regions/<aws_region>/vpcs/<vpc_alias>/tenants/<tenant_name>
terragrunt apply
For more advanced scenarios or troubleshooting, see the full documentation.