Skip to main content
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. agentMinInstances defaults to 0, so an idle queue costs nothing beyond storage.
  • On-demand only by default. spotCapacityPercentage defaults to 0. 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.
AMIs are pinned per region, and only us-east-1 is pinned today. Synth throws in any other region and names the supported list. Deploy the fleet to a *-use1 target.

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:
That writes the SecureString /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:
The construct ships a per-job 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:
That appends one statement to fjall/api/infrastructure.ts and adds the factory import:
Deploy it:
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:
  • agentMinInstances at 0 means an idle queue runs no instances.
  • agentMaxInstances caps concurrent capacity, and defaults to 2.
  • spotCapacityPercentage trades reliability for price. Keep it at 0 for deploy fleets.
  • logRetentionDays and agentLogRetentionDays bound CloudWatch storage.
The buckets, the scaler Lambda and the log groups are the only always-on costs.

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:
Property flags carry no value-less booleans, so pass 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