Skip to content

Configuration

Configuration lives in yaffle.toml at your repository root.

version = 1
[[environments]]
name = "production"
[[triggers.github.push]]
ref = "refs/heads/main"
environment = "production"
[[triggers.github.pull_request]]
branch_pattern = "*"
[[workspaces]]
path = "infra"
environments = ["*"]
version = 1
# Named environments
[[environments]]
name = "production"
[[environments]]
name = "staging"
# Push to main → production
[[triggers.github.push]]
ref = "refs/heads/main"
environment = "production"
# Push to staging → staging
[[triggers.github.push]]
ref = "refs/heads/staging"
environment = "staging"
# All PRs create transient previews
[[triggers.github.pull_request]]
branch_pattern = "*"
# Shared infra runs in both environments
[[workspaces]]
path = "infra/shared"
environments = ["production", "staging"]
variables.cloudflare_zone_id = "abc123"
# Production-only workspace
[[workspaces]]
path = "infra/production"
environments = ["production"]
# App infra runs in all environments (including PR previews)
[[workspaces]]
path = "apps/api/infra"
environments = ["*"]
variables.domain = "{{ environment }}.yaffle.dev"
# Publish a curated platform API to other repos in the same Yaffle org
[[workspaces]]
path = "platform/eks"
environments = ["production", "staging"]
outputs.cluster_endpoint = { visibility = "public", consumers = ["applications/apps/*"] }
outputs.cluster_ca = { visibility = "public", consumers = ["applications/apps/*"] }
# Require approval for production changes
[[approvals]]
workspaces = ["infra/production", "infra/shared"]
environments = ["production"]
approvers = [
"github:user:alice",
"github:team:acme/platform",
]

Always 1.

version = 1

Named environments your workspaces can target.

[[environments]]
name = "production"
[[environments]]
name = "staging"

PR previews are transient environments—they don’t need to be declared.

Run workspaces when a ref (branch or tag) is pushed.

FieldDescription
refFull git ref pattern (must start with refs/heads/ or refs/tags/)
environmentWhich environment to target
# Trigger on branch push
[[triggers.github.push]]
ref = "refs/heads/main"
environment = "production"
[[triggers.github.push]]
ref = "refs/heads/staging/*"
environment = "staging"
# Trigger on tag push
[[triggers.github.push]]
ref = "refs/tags/v*"
environment = "release"

The ref field supports glob patterns. The * wildcard matches any characters except /.

Create transient previews for PRs.

FieldDescription
branch_patternGlob for head branch (* = all PRs)
[[triggers.github.pull_request]]
branch_pattern = "*"

OpenTofu directories Yaffle manages. (Terraform directories work too—OpenTofu is fully compatible.)

FieldDescription
pathPath to OpenTofu/Terraform directory
environmentsWhich environments run this workspace
variablesVariables to inject
outputsOptional per-output visibility rules for cross-repo module consumers
[[workspaces]]
path = "infra/api"
environments = ["*"]
variables.environment = "{{ environment }}"
variables.region = "us-east-1"
  • ["production"] — Only runs in production
  • ["production", "staging"] — Runs in both
  • ["*"] — Runs in all environments, including PR previews

Variables are injected as -var flags. Use {{ environment }} to template the current environment name.

variables.environment = "{{ environment }}"
variables.domain = "{{ environment }}.example.com"

Outputs are internal by default for same-repo consumers. Use outputs.<name> to make specific outputs public for allowlisted cross-repo consumers.

[[workspaces]]
path = "platform/database"
environments = ["production", "staging"]
outputs.connection_string = { visibility = "public", consumers = ["applications/apps/*"] }
outputs.secret_arn = { visibility = "public", consumers = ["applications/apps/*"] }

outputs.<name> fields:

FieldDescription
visibilityinternal or public
consumersRequired for public; allowlist of <repo>/<workspace-pattern>

Rules:

  • internal outputs are only available to downstream workspaces in the same repo
  • public outputs are available only to explicitly allowlisted workspaces in other repos in the same Yaffle org
  • same-repo consumers can still read the full module surface
  • cross-org module sharing is not supported

Require approval before applying.

FieldDescription
workspacesPaths or globs
environmentsWhich environments
approversWho can approve
[[approvals]]
workspaces = ["infra/*"]
environments = ["production"]
approvers = [
"github:user:alice",
"github:user:bob",
"github:team:acme/platform-engineering",
]
<provider>:<type>:<identifier>
ExampleMeaning
github:user:aliceGitHub user alice
github:team:acme/platformGitHub team @acme/platform

Multiple approval blocks can match. Approvers are combined. Any single approver can approve.


Use {{ }} in variable values. Yaffle uses MiniJinja syntax.

TemplateDescriptionExample value
{{ environment }}Environment nameproduction, pr-42
{{ environment_kind }}named or transienttransient
{{ org }}GitHub organization/owneracme
{{ repo }}Repository nameinfra
{{ workspace_path }}Workspace pathinfra/network
{{ branch }}Git branch namemain, feature/login
{{ commit_sha }}Full commit SHAa1b2c3d4...
{{ pr_number }}PR number (null for named envs)42
[[workspaces]]
path = "infra"
environments = ["*"]
variables.env = "{{ environment }}"
variables.bucket = "{{ org }}-{{ repo }}-{{ environment }}"
variables.commit = "{{ commit_sha }}"

MiniJinja filters are supported:

variables.branch_slug = "{{ branch | replace('/', '-') }}"
variables.env_upper = "{{ environment | upper }}"
variables.fallback = "{{ pr_number | default('main') }}"
variables.domain = "{% if environment_kind == 'transient' %}preview-{{ pr_number }}{% else %}{{ environment }}{% endif %}.example.com"

Globs work in ref, branch_pattern, workspaces, and environments:

PatternMatches
*Anything (but not /)
refs/heads/feature/*refs/heads/feature/login, but not refs/heads/feature/a/b
refs/tags/v*refs/tags/v1, refs/tags/v1.0.0
infra/*infra/foo, infra/foo/bar (workspace patterns match across /)