Skip to main content
Complete these first:
  • Deploy Application at least once locally
  • A CI deploy token (minted in the web app under Settings → CI/CD Tokens), which every deploy needs in FJALL_API_KEY, including one that uses AWS credentials from your CI environment

Introduction

The Fjall CLI runs in CI/CD pipelines without extra configuration. When it detects a non-interactive environment (no TTY), it switches to plain text output and returns standard exit codes. Official plugins cover GitHub Actions and Buildkite. For any other CI system, install and invoke the CLI directly. Pipelines authenticate with a CI deploy token. The token doubles as the pipeline’s AWS credential: the CLI exchanges it for short-lived, deploy-scoped AWS credentials in your connected account, so you never store AWS keys in CI.

CI Deploy Tokens

Fjall issues three kinds of token. Only one belongs in a pipeline:

Scopes and Opt-In Grants

Every CI deploy token carries a fixed, least-privilege base scope set: read, deploy, and deploy:oidc:mint. That is enough to run deploys and mint short-lived AWS sessions, and nothing more. It cannot manage team members, tokens, or organisation settings. The base set is stamped at creation and cannot be widened afterwards. Two further grants are opt-in at mint time. Tick them in the mint dialog if your pipeline needs them: A token minted without the grant fails the run with a scope denial. Grants cannot be added to an existing token, so mint a replacement and swap the CI secret. Deploy tokens expire within 90 days. The cap is enforced by the web-app mint form and by the API, which rejects a longer expiry regardless of client. Rotate before expiry by minting a replacement and updating your CI secret.

Minting and Managing Tokens

Minting a deploy token is admin-equivalent (it grants the org-wide AWS deploy path), so it requires an interactive browser session. An owner or admin mints it in the web app under Settings → CI/CD Tokens, optionally restricted to one application, environment, or connected account. The token is shown once, so store it as a CI secret immediately. API-key-authenticated callers, including the CLI, cannot mint deploy tokens, so the CLI has no ci token create command. fjall ci setup writes the workflow file and prints the web-app URL to mint at; the constraints and the expiry are chosen in that dialog. Everything after the mint works from the CLI:
Store the token as FJALL_API_KEY in your CI provider’s secrets.

Token Limits

A token restricted when it is minted carries its restrictions into every deploy. When the CLI exchanges the token for AWS credentials, it names the deployment target’s account and environment, and the exchange is refused if either lies outside the token’s restrictions. A token restricted to accounts can deploy only to a connected account. For any restricted token, the CI token limits card on Settings → CI/CD Tokens decides one more case: a deploy that names no connected account, or, when the token is restricted to environments, names an account connected on a different environment from the one the deploy names. Only an owner or an admin can change the setting.

Workflow Setup

fjall ci setup detects the provider, repository, and application, resolves a deployment target, checks that the target’s AWS account is connected to your organisation, and writes the plugin-form workflow file. It then prints the web-app URL where the deploy token is minted and the secret name to store it under. With --plan it prints the complete workflow file it would write and writes nothing:
How each input is resolved: fjall ci verify dry-runs the auth handshake (credential, principal kind, expiry runway, and application access) before your first pipeline run. Exit 0 means ready, exit 1 means at least one check failed.
fjall ci setup never holds a deploy token: the API only permits minting from a signed-in browser session. The command writes the workflow file and stops; mint the token in the web app (Settings → CI/CD Tokens) and store it as the CI secret it names, then run fjall ci verify.

Choosing the Deployment Target

Two separate inputs answer two separate questions. Getting them confused is the most common CI failure: fjall ci run never falls back to the active target set by fjall target set. That fallback is deliberately absent so a committed activeTarget or an unrelated laptop can never re-aim a pipeline. Pass deploy-target (or environment) on every CI step, or the run fails closed and lists the organisation’s targets. environment is the alternative selector. It resolves the single connected account on that stage, and fails when the stage matches zero or several accounts. On a deploy it additionally chooses the .env.<stage> Docker build-arg tier. region re-qualifies the region within the resolved target’s account. Tier targets accept neither deploy-target nor environment. Organisation infrastructure always deploys to the management account, so the plugins’ tier path accepts only force, verbose, and no-cascade (organisation only) and rejects every other input rather than dropping it.

GitHub Actions

The fjall-tech/fjall-deploy-action is a composite action that installs the CLI and runs it with the right flags. A CI deploy token is the only secret you need. The CLI exchanges it for short-lived AWS credentials scoped to the deploy, and the run is recorded in your Fjall dashboard:
Mint the token in the web app (Settings → CI/CD Tokens) and store it as the FJALL_API_KEY repository secret. Your AWS account must already be connected to Fjall.

With AWS OIDC

If you prefer AWS-native credential management, use GitHub’s OIDC provider to assume an IAM role. No AWS keys are stored, because each run gets short-lived credentials for the role, but the deploy token in FJALL_API_KEY is still a stored secret that expires within 90 days: rotate it as Scopes and Opt-In Grants describes.

With Static AWS Credentials

Store AWS keys as repository secrets. Simplest to set up, but the keys are long-lived and need rotation:
A deploy needs FJALL_API_KEY whichever AWS credential method you use. Without it, the CLI refuses before it authenticates with AWS (Authentication required. Set the FJALL_API_KEY environment variable …). Keep it set on every deploy step.

Split Infrastructure and Code Deploys

Run infrastructure changes and code deploys as separate jobs for faster iteration:

Action Inputs

The action is a thin shim onto fjall ci run for application targets, and onto fjall org|platform|account deploy for tier targets. Every input maps 1:1 to a CLI flag, and the CLI owns command and flag validation.

Action Outputs

A plan run with pending changes exits 2 (surfaced as result: plan-pending), so a plan, approve, apply pipeline can branch on it without treating it as a failure.
cli-version defaults to 39, the CLI major this plugin release is built for, and the same major as current @fjall/components-infrastructure releases. Set it to auto to derive the major from your application’s constructs pin instead, or to an exact version you have validated locally (fjall --version). Avoid latest: it floats across releases, and if it drifts behind your application’s constructs the deploy is refused by the engine compatibility check. Compatibility is a floor rather than a match (the engine major must be at least the constructs major, and nothing caps a newer one), so auto resolves to the lowest major that satisfies your pin: always compatible, but engine work released in a later major stays out of reach until you bump the constructs pin. An exact, validated major is the better default for a team that upgrades deliberately.

Buildkite

The fjall-tech/fjall-deploy-buildkite-plugin installs the CLI and runs it with the right flags.

Minimal Pipeline

Expose FJALL_API_KEY through your Buildkite secrets storage. On clustered or Buildkite-hosted agents, add a cluster secret named FJALL_API_KEY (Agents → your cluster → Secrets) and the plugin fetches it when the env var is absent. On self-hosted agents without cluster secrets, export it from an agent environment hook.
Cluster secrets are readable by every pipeline in their cluster. Keep deploy pipelines in a cluster that runs no PR or untrusted builds, or restrict the secret to this pipeline with a secret access policy.
As on GitHub Actions, a deploy needs FJALL_API_KEY: set the variable or the cluster secret, because with neither the step fails before anything deploys. The cli-version guidance above applies here too.

With AWS OIDC

Staging to Production Pipeline

Plugin Properties

The plugin accepts the same surface as the GitHub action, plus supersede, which only the plugin has. build-args and build-secrets are YAML arrays of strings rather than blocks separated by newlines. The plugin records fjall-deploy:result, fjall-deploy:duration, and any fjall-deploy:approval-token as build meta data (buildkite-agent meta-data get fjall-deploy:result), and a run whose result is plan-pending adds a warning annotation to the build. fjall-deploy:result is success, plan-pending or failure. With supersede set it can also be superseded, which means the build deployed nothing, and then fjall-deploy:superseded-by holds the number of the newer build whose deploy covers it. supersede is for a pipeline that creates one deploy build for each green commit on main, where a deploy that starts late would only repeat a newer one. It takes an object.
after is the key of a step that must have passed on the newer build. token-secret names a Buildkite secret that holds an API token with the read_builds scope, and the plugin reads it with buildkite-agent secret get. barriers is an optional list of paths relative to the repository root. A build skips only when all of these hold.
  • Its creator sent "meta_data": {"fjall-deploy:supersedable": "true"} in the create build request. A build started from the dashboard carries no marker, so a hand rollback always deploys, and a rebuild never skips.
  • The newest of the newer main builds that can still deploy, or have deployed, has passed its after step, and this build’s commit is on that build’s first parent line.
  • No commit on that line, from this build’s commit up to the newer build’s, is a barrier. A barrier changes a file at or under a barriers path, or has a line in its message that starts with deploy-alone. When the line holds one, this build deploys, so a barrier always lands on a commit that was itself deployed.
Any doubt deploys the commit the build was given. That covers a secret it cannot read, a builds list read that fails or runs past its bound (30 s a read, 120 s in all), a git failure, and a clone that stays shallow. The check runs before the CLI installs and prints one line saying whether it skipped and why. An agent keeps the plugin checkout it already made for v29, so one that cloned it before supersede shipped ignores the property and deploys every build until it clones the plugin fresh. The plugin README has the full rule.

Raw CLI Usage (Any CI System)

If you use a different CI system (GitLab CI, CircleCI, Jenkins), install and invoke the CLI directly. For a container build, the runner also needs a reachable Docker daemon and the buildx plugin. A Node image alone does not provide that environment. Configure your provider’s Docker executor or remote Docker service before copying the examples below; Fjall exits 64 when buildx is unavailable. Do not expose a privileged Docker daemon to untrusted pull-request jobs.

Setup

fjall ci run <deploy|destroy|build> <target> is the single CI entry point, and the same command both official plugins invoke for application targets. It forces non-interactive output, skips confirmation prompts, and rejects flags that do not apply to the command you are running (for example, deploy-only flags on a destroy).
The raw fjall ci run positional is application-only. organisation, platform, account, and domain are rejected before authentication with a teaching error. Tier work is noun-verb: fjall org deploy, fjall platform deploy, fjall account deploy. The two official plugins accept the tier words as target and dispatch them to those commands for you.

Key Flags

--no-cascade is not a fjall ci run flag. It exists only on the organisation tier leaf: fjall org deploy --no-cascade. Invoking fjall deploy api --target production-use1 --non-interactive --skip-confirmation directly still works. fjall ci run wraps the same engine with CI-safe defaults.

Exit Codes

Exit 4 means the deploy stopped at the destruction gate. It is a decision, not a failure, and the cure depends on the outcome printed with the ticket. When consent was withheld, surface the printed ticket to a human and re-run with the named consents once reviewed, bound with --destruction-consent-digest. The ticket ends with a ticket digest: line, printed by a --plan run and by the withheld run itself, so the digest to bind against is already in the job log. A consented pin has already written the config change: commit it and re-run without re-supplying the consent. A declined, invalid, or not-yet-executable consent prints what to change. Always follow the printed cure.
Never auto-inject --remediate or --allow-replace in a pipeline. A consent baked into CI silently approves the next unrelated change that replaces a resource of the same name. See Exit code 4 in CI.

Reading the Output

From fjall 38, the CLI splits its two streams by what a line is, not by which command wrote it. Never scrape progress off stdout. A failed command leaves stdout empty, so branch on the exit code and never on whether stdout had content. The one exception is a machine protocol the caller asked for, where a failure can arrive as a self-describing document on stdout. On fjall ci run, and on every other fjall ci command, that protocol is --agent. Elsewhere in the CLI, --mcp-protocol and a --json / --output json document do the same job on the commands that carry them. A failure written as prose is never on stdout. A successful deploy of an application with a website URL prints that URL on stdout, so a pipeline can capture it:
A deploy writes nothing to stdout when the application has neither a load balancer nor a CloudFront distribution, such as a Lambda-only or queue-worker application, and still exits 0. Branch on the exit code, not on whether the captured variable is empty. Colour is decided per stream, so redirecting one leaves the other alone:

Environment Variables

Example: GitLab CI

Example: CircleCI

Set FJALL_API_KEY as a project secret in either system.

Setting Up AWS Credentials

Whichever CI system you use, the Fjall CLI needs AWS credentials to deploy, and FJALL_API_KEY whichever way it gets them: AWS credentials you supply replace only the AWS session the deploy token would mint. Here are the common approaches, from most to least recommended: Set FJALL_API_KEY to a CI deploy token and skip AWS credential setup entirely. The CLI exchanges the token for short-lived, deploy-scoped AWS credentials in your connected account, and every run is tracked in your dashboard.

AWS OIDC Federation

Most CI systems support OIDC federation with AWS, which avoids storing AWS keys. The deploy token in FJALL_API_KEY is still stored, and must be rotated before it expires. To set it up:
  1. Create an IAM OIDC identity provider for your CI system
  2. Create an IAM role with a trust policy scoped to your repo or pipeline
  3. Use your CI’s OIDC integration to assume that role

Static Credentials

Store AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as CI secrets. Simpler to set up but requires key rotation.
Ambient AWS credentials win, silently. When both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are present in the environment, the CLI authenticates with those two keys and returns before profile resolution runs. The profile your deployment target resolves to is skipped, and the short-lived AWS session a deploy token would have minted is never requested.In 39.0.8 nothing compares the keys’ account with the target’s for an application deploy, destroy, rollout or rollback: they run in whichever account the keys belong to, whatever --target (the action’s deploy-target) names. fjall account deploy also runs in the keys’ account, whatever its --target names, but refuses keys for the organisation’s management or platform account; unset them to deploy where the target says. fjall platform deploy refuses unless the keys belong to the platform account, and fjall org deploy unless they belong to the management account; on 39.2.2 the organisation deploy checks the account before recording its start or running organisation setup (unlike 39.0.8) (Deploy the organisation).This bites in CI, where runners often carry ambient AWS credentials for a different account than the one you are deploying to. Unset AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN on the deploy step unless they are the credentials you want the deploy to use.Region is part of the trap too. An application deploy takes --region (the action’s region input) when you give one; otherwise every deploy on these keys runs in AWS_REGION, else AWS_DEFAULT_REGION, else us-east-2, never the region your deployment target names.

Instance Profiles

The CLI never authenticates a deploy with an EC2 instance profile, so CI agents on EC2 (for example, self-hosted Buildkite agents) deploy with FJALL_API_KEY like any other runner. A deploy needs no permissions on the instance profile, so leave deploy permissions off it rather than open them to every job the agent runs, as the Buildkite Agent Fleet does.

Next steps

Deploy an Application

Run a full deploy locally before wiring it into CI/CD.

ci Command Reference

Every fjall ci subcommand, token verb, and flag.

Deployment Targets

How Fjall derives AWS profiles and names deployment targets.

Destructive Changes

Handle exit code 4 and the consent ceremony in a pipeline.