Getting started
Install Gondola on AWS.
The Terraform module installs the controller and runner infrastructure. It uses your existing network and does not create a VPC or NAT gateway.
| Deployment boundary | Current support |
|---|---|
| AWS | One account and one region per controller deployment |
| GitHub | GitHub.com organization or user account; private GitHub App |
| Runners | Linux x64 and ARM64; one ephemeral EC2 instance per job |
| Network | Existing VPC and outbound HTTPS; no inbound endpoint |
Prerequisites
- Terraform 1.5 or newer and AWS credentials authorized for the module resources.
- An existing VPC with controller and runner subnets that can reach GitHub, AWS APIs, and the configured image registries over HTTPS.
- Subnets in at least two Availability Zones for the default two-controller topology.
- A digest-pinned Gondola controller image and a compatible digest-pinned runner image.
- Authority to create and install a private GitHub App for the repositories Gondola will serve.
- A protected remote Terraform backend, versioning, and a recovery procedure appropriate for your environment.
Start in a non-production AWS account and GitHub repository. Review the Terraform plan, IAM policies, egress path, and runner image before granting access to sensitive workloads.
Plan the inputs before applying
| Input group | Decide and review |
|---|---|
| Identity | GitHub owner, App client ID, installation ID, private-key secret ARN |
| Controller | Name, region, image digest, VPC, two subnets, task count, log retention |
| Each fleet | Label, architecture, capacity mode, instance type, AMI, runner image digest |
| Trust | Subnets, security groups, runner role, allowed egress, repository scope |
| Guardrails | Minimum, maximum, lifetime, root-volume size, metrics, alarms, cost tags |
1. Create the GitHub App
Run the local setup flow from a Gondola binary. It opens GitHub’s App manifest flow, verifies the installation, and writes the private key plus installation identifiers under .gondola/github-app.
gondola github-app setup \
--owner example \
--account-type organization \
--name "Example Gondola Runners"The organization App requests repository Administration read/write, organization self-hosted runners read/write, and Metadata read access. A user-account App requests repository Administration read/write and Metadata read access. Webhooks and OAuth user authorization are not enabled. Install it only for the repositories Gondola should serve.
private-key.pemis secret and is written with mode0600.installation.jsoncontains the non-secret client and installation identifiers.- The App client ID is the value beginning with
Iv1, not the numeric App ID.
2. Store the private key
Put the PEM directly in AWS Secrets Manager. Pass only its ARN to Terraform so the key value does not enter Terraform state.
aws secretsmanager create-secret \
--name gondola/github-app-private-key \
--secret-string file://.gondola/github-app/private-key.pemAfter verifying the secret, securely remove the local PEM according to your organization’s credential-handling procedure.
Prefer a version-qualified secret reference when your rotation process supports one. If the secret value changes at the same ARN, change deployment_generation_nonce so ECS creates a new controller generation that reads the new version.
3. Configure Terraform
Use an immutable release reference and OCI digest supplied with your Gondola release. The placeholders below must be replaced before applying.
module "gondola" {
source = "github.com/gregtuc/gondola//deploy/terraform?ref=<release>"
name = "gondola-production"
vpc_id = var.vpc_id
subnet_ids = var.controller_subnet_ids
container_image = "<registry>/gondola@sha256:<digest>"
github_config_url = "https://github.com/example"
github_app_client_id = var.github_app_client_id
github_app_installation_id = var.github_app_installation_id
github_app_private_key_secret_arn = aws_secretsmanager_secret.github_app_private_key.arn
desired_count = 2
require_image_digest = true
require_runner_image_digest = true
fleets = {
linux_x64 = {
scale_set_name = "example-linux-x64"
architecture = "x64"
capacity_mode = "spot-with-on-demand-fallback"
instance_type = "m7i.large"
subnet_ids = var.runner_subnet_ids
min_runners = 0
max_runners = 10
}
}
}If you bring an existing runner instance profile, set both its profile ARN and role ARN. Gondola receives iam:PassRole only for configured runner roles. Prefer GitHub OIDC for job-specific AWS access instead of adding standing permissions to every runner.
4. Review the network path
Controller subnets need DNS plus outbound HTTPS to GitHub, AWS APIs, and the controller registry. Runner subnets additionally need the endpoints used by workflow dependencies, source downloads, caches, and artifacts. No inbound rule is required by Gondola.
- The two default controller subnets are in different Availability Zones.
- Security-group egress and network ACLs permit the reviewed destinations.
- Private subnets have a measured egress path; Gondola does not create a NAT gateway.
- Runner subnets have enough free addresses for the configured maximum concurrency.
- The selected AMI and runner container image both support the fleet architecture.
For Spot fleets, create the account’s EC2 Spot service-linked role once if it does not already exist:
aws iam create-service-linked-role \
--aws-service-name spot.amazonaws.com5. Plan and apply
terraform init
terraform validate
terraform plan -out=gondola.tfplan
terraform apply gondola.tfplanReview the exact ECS task definition, IAM permissions, launch templates, security groups, log group, secret references, and coordination table. Do not apply a plan that introduces an unexpected mutable image tag, broad pass-role permission, public ingress, or new network path.
Resources and useful outputs
| Output | Use |
|---|---|
cluster_arn / service_name | Inspect controller tasks, deployments, and circuit-breaker events |
log_group_name | Locate structured controller logs |
coordination_table_name | Read leader and generation state during incident triage |
deployment_generation | Correlate tasks, leases, runners, and upgrades |
fleet_scale_set_names | Copy the stable labels into GitHub workflows |
runner_launch_template_ids | Verify the instance policy and exact template versions |
alarm_arns | Connect optional fleet alarms to the operator’s response path |
The controller reports ready only after every fleet has initialized its GitHub scale set, validated its session, completed a queue poll, and reconciled AWS capacity. A running ECS task is not by itself proof of a usable control plane.
6. Verify a disposable job
jobs:
acceptance:
runs-on: example-linux-x64
steps:
- uses: actions/checkout@<reviewed-sha>
- run: make test- The ECS service reaches its desired count and the active controller reports ready.
- The workflow is assigned to the intended fleet and EC2 launch-template version.
- The runner receives only the fleet IAM role and network policy you approved.
- The EC2 instance terminates after the job and no offline runner remains registered.
- CloudWatch logs contain no credentials, JIT configuration, or workflow payloads.
7. Record the installation
Keep the Terraform state location, release and image digests, GitHub App owner and installation ID, secret ARN, deployment generation, scale-set names, fleet owners, network decision, rollback digest, and support route in your operating record. Do not copy the private key or JIT configuration into that record.
Before admitting sensitive workloads, test controller loss, a normal upgrade, rollback, one job on every fleet, and runner termination. Then set budgets, log retention, and any optional alarms deliberately.