Buildkite is a CDK construct inside a Fjall application, not a standalone stack. Add it with fjall add buildkite, then ship it with fjall deploy <app>. There is no cdk deploy step and no BuildkiteDefaultProps export.Overview
The fleet runs Buildkite agents on the pinned Elastic CI Stack AMIs inside your own AWS account. Agents poll the Buildkite API for jobs, so the fleet creates no load balancer and needs no inbound access. Three properties define the posture:- Scale to zero.
agentMinInstancesdefaults to0, so an idle queue costs nothing beyond storage. - On-demand only by default.
spotCapacityPercentagedefaults to0. A spot reclaim mid-deploy is a rollback incident, so raise it only for idempotent, auto-retried jobs. - No secret transits synth. The construct receives SSM parameter names. Token values move from SSM to the instance at job runtime.
What it provisions
The instance profile deliberately carries no registry credentials and no
AmazonSSMManagedInstanceCore. Deploy jobs authenticate through server-minted Fjall credentials, exactly as they do on hosted agents.
Prerequisites
- Node 22 or later
- A Fjall application with networking enabled (the fleet attaches to the application’s VPC)
- A Buildkite organisation slug, such as
acme-eng - A cluster-scoped Buildkite agent token, stored in SSM before the first deploy
Store the agent token
The agent token is load-bearing: the boot script and the scaler both read it, and an unresolvable parameter fails synth. Provision it first with the Fjall secrets tooling:/api/agents/agent-token, which is the path the construct derives by default from the application name. Store the Fjall API key the same way when your pipelines call the Fjall CLI:
env hook into the managed secrets bucket. The hook reads both parameters at the start of every job, so rotating a token takes effect on the next job with no instance replacement.
Quick start
Add the fleet to an existing application. Both--buildkiteQueue and --buildkiteOrgSlug are required, and a missing one is refused before the file is written:
fjall/api/infrastructure.ts and adds the factory import:
App.addBuildkite places the fleet in the application’s compute stack and injects the application id, the shared alarm topic and the cost-allocation environment. Defaults from the schema are applied at synth, so infrastructure.ts keeps only the properties you set.
Configuration
Every property is a camelCase key on the construct’s schema, which is.strict(). An unknown key is rejected at write time by fjall add and at synth by the construct, so a stale property name fails loudly rather than being ignored.
Set properties either as flags on fjall add and fjall modify, or directly in the factory call:
Required
Secrets and identity
Both parameter paths must start with
/. An unresolvable agent-token path throws at synth. An unresolvable API-key path skips the env hook and emits a warning instead.
Capacity
agentMinInstances must be less than or equal to agentMaxInstances. The pair is validated together, so an inverted range is rejected at synth.
Instance lifecycle
Instances leave the group by self-termination or by lifetime replacement. The scaler runs with scale-in disabled, so it never reduces desired capacity.
Agent behaviour
Plugins and Docker
The ECR and Docker login plugins default off because the instance profile confers no registry credentials. Turn them on only when the fleet’s jobs genuinely need instance-level registry access.
Storage and logs
agentLogRetentionDays accepts one of 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653. Retention is always applied, so pick a longer value rather than looking for a disable switch.
Scaler
Alarms and governance
rolePermissionsBoundaryArn is this stack’s own knob: it applies a boundary you nominate to the roles the agent fleet creates, and it is off unless you set it. It is unrelated to the deploy-role permissions boundary, which Fjall installs and attaches to FjallDeploy<orgId> by default and which you do not configure here.Cost controls
The fleet has no fixed monthly floor. Four properties drive the bill:agentMinInstancesat0means an idle queue runs no instances.agentMaxInstancescaps concurrent capacity, and defaults to2.spotCapacityPercentagetrades reliability for price. Keep it at0for deploy fleets.logRetentionDaysandagentLogRetentionDaysbound CloudWatch storage.
Customisation
A second fleet for a different queue
Each fleet serves one queue. Add a second statement with its own name and queue:An explicit token path
Point the fleet at a parameter outside the derived namespace, for example a token shared by several applications:Container workflows
Turn on the registry plugins when jobs pull private images from the account’s own ECR:true or false explicitly.
Monitoring
The fleet ships two CloudWatch alarms on five-minute periods:
Both are wired to the shared alarm topic unless
alarmSnsTopicArn overrides it. Without a topic they still appear in the console, silently.
Agent runtime logs land in /buildkite/* log groups in the fleet’s region. Scaling activity and instance health are visible under EC2 Auto Scaling Groups.
Managing an existing fleet
A statement written in the older
app.addBuildkite({ … }) form is listed as (unmanaged). Re-add it through fjall add buildkite to bring it onto the managed path.
Next Steps
fjall add
Add resources through the codemod engine
fjall secrets
Store the agent token and API key in SSM
fjall ci
Scaffold pipelines and mint deploy tokens
CI/CD Integration
Deploy from Buildkite with scoped tokens