Skip to main content
The production posture: the config lives in git, every change is planned on a pull request, and the apply runs from CI on merge. No credentials are stored in the pipeline.

Authentication

Each job authenticates with short-lived OIDC tokens issued by GitHub Actions for that run. The pull-request and merge contexts map to different identities: The apply job exchanges its GitHub token for a short-lived Synthetiq token through the service account’s trust; deleting the trust revokes access immediately.
The identity split is the security boundary — not workflow YAML or CLI flags. A PR branch controls its own workflow file, so the pull_request subject must map only to the plan role: no apply permissions, no Synthetiq identity. The apply identities are reachable only from merged code.

One-time setup

1

Create the AWS roles

Two IAM roles in the target account, trusting GitHub’s OIDC provider (token.actions.githubusercontent.com), scoped to your repository:
  • synthetiq-infra-plan — trusted for repo:<github-org>/<repo>:pull_request; policy from synthetiq infra permissions --stage generate
  • synthetiq-infra-apply — trusted for repo:<github-org>/<repo>:ref:refs/heads/main; policy from synthetiq infra permissions --stage provision
2

Create the Synthetiq service account and trust

Find the role id for CI Provision Apply:
Create the service account with that role:
Create the OIDC trust so CI can authenticate as it:
See Service Account.
3

Add the workflow

The published Synthetiq workflow wires up both jobs with the correct triggers, OIDC permissions, and a concurrency guard so two changes can’t apply at once:

Nested directory layout

By default the infra root — package.json, .npmrc, and _infra/ — sits at the repository root. To keep it in a subdirectory instead (for example, inside a shared infrastructure monorepo), move those files together into that directory and point the workflow at it with working-directory:
They move as a unit: the CLI installs from the package.json next to _infra/, and it discovers the config by walking up from wherever it runs. Only .github/workflows/synthetiq-infra.yml stays at the repository root — GitHub requires workflow files there. Point the workflow at the infra root — add working-directory to the with: block:
Prefix the trigger paths with the same directory:
GitHub Actions path filters are static — they can’t read the working-directory value, so you must prefix them by hand. If they don’t match the nested path, editing your config won’t trigger the plan or apply job, and the workflow will silently never run.
Nothing else changes: synthetiq.yaml, the AWS roles, and the Synthetiq service account are all unaffected. Run local CLI commands (npm install, synthetiq infra init) from inside the infra root — the CLI finds _infra/ from any directory at or below it.

The flow on every change

  1. Edit _infra/synthetiq.yaml in a branch; open a PR.
  2. The plan job runs synthetiq infra generate: change sets are parked in your AWS account, the rendered CloudFormation template and changeset are committed to the branch, and a single PR comment (updated on later pushes) summarizes the impact — change counts by area and any replacements. The full template diff is in the PR’s Files changed.
  3. Review and merge through your normal branch-protection process.
  4. The apply job runs synthetiq infra provision, executing exactly the parked change set. If the stack moved since the plan, CloudFormation refuses it and the job fails asking for a re-plan. On the first provision, the job log ends with the three app DNS records to create.
  5. Re-running with no config change is a green no-op — schedule the workflow for a free drift check.
For a second gate after merge, put the apply job behind a GitHub environment with required reviewers.

Staying current

Synthetiq publishes infrastructure updates as new CLI versions (all packages share one version number); an update can mean a diff to your stacks. An upgrade is just another pull request:
  1. Pin @synthetiq/cli in the infra repo (package.json + lockfile, installed by both jobs). Never @latest — a floating version injects surprise diffs into unrelated PRs and lets the plan and apply jobs run different versions.
  2. Upgrades arrive as version-bump PRs — point Renovate or Dependabot at the pin. Because package.json/package-lock.json are in the workflow’s trigger paths, the bump alone runs the plan job on the new version, and the PR comment shows the exact diff that release causes in your account.
  3. Review and merge like any other change; security patches are just fast merges.
  4. Same config + same version is a green no-op, so scheduled runs only go red for genuine drift.
Two guards backstop this: CloudFormation refuses a parked change set if the stack moved since planning, and provision refuses a changeset generated by a different CLI version (--allow-version-skew overrides).

Security model

  • No stored credentials — AWS and Synthetiq access are per-run OIDC exchanges; tokens expire in minutes.
  • Immediate revocation — delete the trust or demote the service account’s role and the next exchange fails.
  • The AWS side is yours — your OIDC provider, roles, and trust policies; Synthetiq only specifies the policy contents.
  • Fork PRs are inert — GitHub withholds OIDC tokens from fork pull requests.
  • Audit trail — tokens minted through a trust carry the trust, issuer, and subject in their claims, so every CI action is attributable to the repository and ref that performed it.