Control Plane
The AWS compositions create task definitions before they create long-running services. This keeps workloads that require secrets and a current schema from starting during the first apply.
Bootstrap the database
After populating secrets, run the database bootstrap task once. It idempotently creates the non-administrative helmr_app role from database_url, using the RDS-managed master credential inside the VPC.
aws ecs run-task \
--cluster "$(tofu output -raw controlplane_cluster_name)" \
--task-definition "$(tofu output -raw database_bootstrap_task_definition_arn)" \
--launch-type FARGATE \
--network-configuration "$(jq -cn \
--argjson subnets "$(tofu output -json controlplane_task_subnet_ids)" \
--argjson securityGroups "$(tofu output -json controlplane_task_security_group_ids)" \
--arg assignPublicIp "$([ "$(tofu output -raw controlplane_assign_public_ip 2>/dev/null || printf false)" = "true" ] && printf ENABLED || printf DISABLED)" \
'{awsvpcConfiguration:{subnets:$subnets,securityGroups:$securityGroups,assignPublicIp:$assignPublicIp}}')"
Wait for the task to stop and confirm its container exit code is zero. Do not infer success from aws ecs run-task returning successfully.
Run migrations
Use the same network configuration with migration_task_definition_arn:
aws ecs run-task \
--cluster "$(tofu output -raw controlplane_cluster_name)" \
--task-definition "$(tofu output -raw migration_task_definition_arn)" \
--launch-type FARGATE \
--network-configuration "$(jq -cn \
--argjson subnets "$(tofu output -json controlplane_task_subnet_ids)" \
--argjson securityGroups "$(tofu output -json controlplane_task_security_group_ids)" \
--arg assignPublicIp "$([ "$(tofu output -raw controlplane_assign_public_ip 2>/dev/null || printf false)" = "true" ] && printf ENABLED || printf DISABLED)" \
'{awsvpcConfiguration:{subnets:$subnets,securityGroups:$securityGroups,assignPublicIp:$assignPublicIp}}')"
Again, wait for a zero exit code. Run migrations for the exact Control Plane image before starting or updating services.
Enable services
After both one-off tasks succeed:
create_controlplane_service = true
Apply the change with the same variable-file selection used for the first
apply. The quickstart profile automatically loads terraform.tfvars; the
standard profile in this guide uses standard.tfvars. The composition starts
separate helmr-controlplane and helmr-dispatcher ECS services using
controlplane_desired_count and dispatcher_desired_count.
# infra/aws/quickstart
tofu apply
# infra/aws/standard
tofu apply -var-file=standard.tfvars
CONTROL_PLANE_URL="$(tofu output -raw controlplane_url)"
curl -fsS "$CONTROL_PLANE_URL/healthz"
curl -fsS "$CONTROL_PLANE_URL/readyz"
/healthz reports process liveness. /readyz is the traffic-readiness check after the database, Redis/Valkey, and schema are ready. Keep at least one dispatcher task when runs or schedules are used.
After readiness passes, sign in and finish first-organization setup using the setup token. Then add workers for task execution.