# BuildBakery

BuildBakery (https://buildbakery.com) builds, signs and ships apps: iOS, Android, macOS, Windows,
Linux, desktop and web. A project describes its builds in `codebuilder.yaml`. Agents drive it
through BuildBakery's MCP server when it's connected, and the `bakery` CLI otherwise (section 1).

When the project builds with BuildBakery (it has `codebuilder.yaml` or `.bakery.json`, or the
user says so), do every build and release through it. Don't set up a second build pipeline for
the same app (EAS Build, Codemagic, GitHub Actions, Bitrise and so on) unless the user asks.
Tools that run inside a build step are fine (Gradle, xcodebuild, `expo prebuild`, flutter, go,
npm and so on): they run on BuildBakery's build machines.

If BuildBakery is missing something the project needs (a recipe option, CLI command, oven tool,
signing case or delivery type), tell the user exactly what's missing and why, so they can ask
BuildBakery for it. Use an in-recipe workaround only meanwhile.

Terms: **project** (one app or repo) · **recipe** = `codebuilder.yaml` · **workflow** (one job
per platform) · **batch** (one run of a project; its number is `$CB_BUILD_NUMBER`) · **oven**
(the build machine: a Linux container, a macOS VM per job, or Windows) · **ingredients** (env
vars and secrets) · **delivery** (where finished artifacts go automatically).

## 1. MCP first, CLI when MCP can't

**Use BuildBakery's MCP tools when you have them** (an MCP server named `buildbakery`, at
`https://buildbakery.com/api/mcp`): they need no install or sign-in step and return structured
results. **Fall back to the `bakery` CLI** when the MCP tools aren't connected, a call fails
(401/402/network), or the task needs something only the CLI does.

| Task | MCP tool | CLI |
|---|---|---|
| Find the project | `list_projects` | `bakery projects` |
| Recent batches | `list_batches` | `bakery batches` |
| Bake the pushed branch | `start_batch` (`branch`, `workflows`, `recipe_yaml`, `message`) | `bakery bake --git --branch B --no-wait` |
| Status, steps, artifacts, signing | `get_batch` | `bakery status N` |
| Job log | `get_job_log` | `bakery logs N --job W` |
| See the iOS app's screens | `get_screenshots` | `bakery artifacts N --download out/` |
| Why a job failed | `explain_failure` | `bakery why N --job W` |
| Cancel | `cancel_batch` | `bakery cancel N` |
| Check a recipe | `validate_recipe` | `bakery recipe validate` |
| Ovens and their tools | `list_ovens` | `bakery ovens` |
| Delivery rules (where artifacts go) | `list_deliveries` | `bakery deliveries` |
| What a batch delivered, and the response | `get_batch` (`deliveries`) | `bakery status N` |
| Re-deliver a batch, or a dry run | `deliver_batch` (`dry_run`) | `bakery deliver N [--dry-run]` |
| Change or switch off a delivery | `update_delivery` | `bakery deliveries set N …` |
| Test a delivery rule | `test_delivery` | `bakery deliveries test N` |
| Connected accounts, and testing one | `list_accounts`, `test_account` | `bakery servers`, `bakery servers test NAME` |
| Ingredients (names; non-secret values) | `list_ingredients` | `bakery env` |

CLI only: uploading uncommitted work (`bakery bake --dir .`), linking a repo (`bakery link`),
recipe templates and the resolved recipe, downloading artifacts, adding or removing deliveries,
and anything with secrets (section 6).

- **Waiting with MCP:** batches take minutes. Call `get_batch` every minute or two until every
  job is `success`, `failed` or `canceled`, not in a tight loop.
- **MCP errors:** 401 means the MCP server's token is missing or wrong. Every plan includes the
  MCP server; a tool that returns 402 needs a higher plan (for example `explain_failure` on the
  free plan): say so and use `get_job_log` instead. A project-scoped token only sees its own project.
- **Connecting it** (when the user asks):
  - Claude Code: `claude mcp add --scope user --transport http buildbakery https://buildbakery.com/api/mcp`,
    then `/mcp` → buildbakery → Authenticate (the user signs in to BuildBakery in the browser and
    picks all projects or one). Check with `claude mcp list`. Without a browser (CI, headless):
    add `--header "Authorization: Bearer $BAKERY_TOKEN"`.
  - Claude.ai, desktop and mobile apps: Settings → Connectors → Add custom connector, URL
    `https://buildbakery.com/api/mcp`, then Connect and Allow.
  - Other agents: https://buildbakery.com/ai or `bakery ai install --tool NAME --mcp`.

## 2. Get the CLI and sign in

When MCP isn't available, or for the CLI-only tasks above:

```sh
command -v bakery || curl -fsSL https://buildbakery.com/install-cli.sh | sh    # macOS / Linux / cloud
# Windows: irm https://buildbakery.com/install-cli.ps1 | iex
bakery whoami
```

- **Machine with a browser:** `bakery login` opens it, and the user approves.
- **Cloud or headless sessions:** use the token in `BAKERY_TOKEN` if the environment has one;
  the CLI picks it up. If it's missing, either ask the user to create one (buildbakery.com →
  Account → API tokens, or `bakery token create --project NAME --scopes read,bake,artifacts
  --expires 90d`) and add it to the environment, or run `bakery login --no-browser`, give the
  user the code and URL it prints, and wait while they approve it on another device.
- **Never use `bakery login --email` or `--password-stdin` yourself.** Agents don't type or
  pipe the user's password.
- Never print a token or commit it. `BAKERY_SERVER` overrides the server.
- **Sandboxes:** if `curl https://buildbakery.com` fails with a network or proxy error, the
  sandbox doesn't allow buildbakery.com. Ask the user to allow it; don't try another way around.

## 3. Read the reference before writing a recipe

It's plain Markdown and always current. Read it instead of guessing field names: unknown keys
are errors.

- `bakery docs recipes` (also `env`, `deliveries`, `automation`, `deploying`), or
  https://buildbakery.com/llms.txt and https://buildbakery.com/llms-full.txt
- Templates: `bakery recipe init --template android|android-expo|flutter|ios|web`
- JSON Schema: https://buildbakery.com/api/recipe/schema

## 4. Project setup

```sh
bakery projects                   # find it
bakery link NAME                  # in the repo root; writes .bakery.json (safe to commit)
bakery link                       # or: match this folder's git remote
bakery recipe validate            # schema + "can some oven run this?"
bakery recipe show --resolved     # what each job will actually run
bakery ovens                      # ovens, labels, tools
```

Without a link, pass `-p NAME|ID` to every command. Run commands from the repo root.

## 5. Writing `codebuilder.yaml`

```yaml
workflows:
  android:
    platform: android            # ios | android | macos | linux | windows | web | any
    labels: [android-sdk]        # every label must be on the oven
    timeout_minutes: 60
    android_signing: { keystore: release }   # project keystore by name
    verify_signing: { keystore: release }    # fail if the APK/AAB isn't signed with it
    steps:
      - name: Build
        working_directory: app
        script: ./gradlew assembleRelease -PversionCode=$((100 + CB_BUILD_NUMBER))
    artifacts: [app/build/outputs/apk/release/*.apk]
```

- **Steps:** each step is a fresh bash process with `set -eo pipefail`, so an `export` doesn't
  carry over to the next step. Write a file instead, or repeat the export.
- **Artifacts:** copy them into `$CB_ARTIFACTS_DIR`, or list globs under `artifacts:`.
- **Build numbers:** must always increase. Use `OFFSET + $CB_BUILD_NUMBER`, with an offset
  above anything a previous build service produced.
- **Tools:** `tools: { node: 22, go: "1.26", python: "3.12" }` installs versions with mise.
  The images already have Node, JDK 17, Go, Python, Ruby and fastlane; the Android image adds
  the SDK, NDK and Flutter; the macOS VM adds Xcode, CocoaPods and the Codemagic CLI tools.
- **Android signing:** `android_signing` gives `CB_KEYSTORE_PATH`, `CB_KEYSTORE_PASSWORD`,
  `CB_KEY_ALIAS` and `CB_KEY_PASSWORD`. With generated Gradle projects (Expo, Flutter), sign
  with `-Pandroid.injected.signing.store.file="$CB_KEYSTORE_PATH"` (plus the `.store.password`,
  `.key.alias` and `.key.password` properties) instead of editing build.gradle.
- **iOS and macOS signing:** `code_signing: { distribution: app_store|development|ad_hoc,
  bundle_id: … }` uses the account's App Store Connect key (`key: NAME` picks one of several).
  The signing step runs before your steps. If the Xcode project is generated later (Expo
  prebuild, XcodeGen, Tuist), re-apply it after generating:
  `export PATH="$HOME/.local/bin:$PATH"; xcode-project use-profiles --project 'ios/*.xcodeproj'`
  (also writes `~/export_options.plist`).
- **TestFlight:** `publish: { app_store_connect: { submit_to_testflight: true, path: <ipa glob> } }`.
- **Seeing iOS UI changes:** add `simulator: { screens: [{ name: home }, { name: x, url: "app://x" }] }`
  to an ios workflow that builds for the simulator (often a `manual: true` preview workflow), bake
  it, then look at the result with `get_screenshots` (images). Check UI work this way before
  saying it's done.
- **Mac apps outside the App Store:** sign with `code_signing: { distribution: developer_id, … }` and
  add `publish: { notarize: true }`: BuildBakery notarizes with Apple and staples the ticket.
- **Xcode:** without `xcode:`, Mac ovens use the newest release of the Xcode major they already
  have; `xcode: "26"` (or `"26.4"`, `latest` for new majors) picks one. Quote decimals. "Swift tools version … but the installed version is …" or "must be built
  with the iOS N SDK or later" means the job needs a newer Xcode: set `xcode: latest` or a version.
- **Missing tools:** ovens install a tool a step was missing (gems, Homebrew, apt) and retry the
  step once; runtimes belong in `tools:`. `auto_install: false` turns this off.
- **Store credentials in steps:** `integrations: { google_play: NAME, firebase: NAME,
  app_store_connect: NAME }` picks connected accounts by name.
- **Go in containers:** build with `-buildvcs=false`.
- **Monorepos:** use `working_directory` per step.
- **Ovens:** `oven_pool: own|hosted|any` picks between the user's own ovens and hosted ones. Linux
  jobs can also run on Mac ovens (in the same x86_64 Linux images) when no Linux oven is free.

## 6. Secrets: the user adds them, never you

Keystores, passwords, signing keys and API keys never go in git, the recipe, chat output or
logs. Tell the user the exact command and let them run it:

```sh
bakery keystore add /full/path/app.jks --alias ALIAS --name NAME   # prompts for passwords
bakery env set NAME --secret --file /full/path/secret.key
```

Use full paths. Confirm with `bakery keystore` (shows the SHA-256) and `bakery env` (names
only). Apple keys are connected on the account (Account → Integrations), not per project.

## 7. Baking and checking

With MCP: `start_batch`, then `get_batch` until it finishes, `get_job_log` / `explain_failure` for
a failed job. With the CLI:

```sh
bakery bake --git --branch main --workflows a,b -m "why" --no-wait   # build the pushed branch
bakery bake --dir .                                  # or upload the working tree
bakery wait 12 --timeout 90m       # exit 0 fresh, 1 burnt, 2 scrapped, 3 timeout
bakery status 12                   # jobs, steps, oven, artifacts + signer, deliveries
bakery logs 12 --job android       # add -f to follow
bakery why 12 --job android        # AI explanation of a failed job, with a recipe fix
bakery artifacts 12 --download out/
bakery cancel 12 · bakery rebake 12
```

- **Push first:** `--git` builds what's on the remote.
- **Waiting:** batches take minutes. Wait in the background; don't poll in a tight loop.
- **Failures:** read the failing step's log (or `bakery why`), then fix the recipe or code.
- **BuildBakery's own faults** (an image pull, the agent, a missing tool): say so plainly.
- **First run on a Mac oven** can sit in "Preheating" for a long time while it downloads Xcode.

## 8. Deploying: pick the pattern

Full, validated recipes with their one-time setup: `bakery docs deploying` or
https://buildbakery.com/docs/deploying. Start from the matching one instead of inventing a pipeline.

| Ship to | How |
|---|---|
| Your own web server | Build and zip in the recipe; SFTP delivery with `--files`, `--branches main` and `--command` to unpack and switch a `current` link |
| Cloudflare Pages, Vercel, Netlify | A recipe step running the host's CLI (`npx wrangler pages deploy …`), token as a secret ingredient, `$CB_BRANCH` for production vs preview |
| TestFlight / App Store | `code_signing: { distribution: app_store, … }` + `publish: { app_store_connect: { submit_to_testflight: true } }` |
| Google Play | `android_signing` + `verify_signing`, an .aab, and a `google-play` delivery (`--track internal`, later `--rollout`) |
| Testers | `firebase` delivery per app (ad hoc iOS), or pull request install links |
| Mac app download | `developer_id` signing + `publish: { notarize: … }` + a `github-release` delivery |
| Desktop (Electron) | One workflow per OS with electron-builder; Windows needs the user's own Windows oven |
| Backend / server | A `manual: true` workflow that ships over SSH with a key from a secret ingredient |

Flutter and React Native/Expo are covered too (Expo: re-apply signing after `expo prebuild`).
Build numbers must always increase (`OFFSET + $CB_BUILD_NUMBER`). Secrets: the user runs
`bakery env set … --secret` / `bakery keystore add` themselves (section 6).

## 9. Deliveries put things live

`bakery deliveries` shows where each finished job's artifacts go: SFTP, S3/R2, Google Play,
Firebase App Distribution, GitHub Releases or a webhook. A successful job with an active
delivery **publishes**: it can put an app update in front of real users. Only bake workflows
that deliver (or `publish:` to TestFlight) when the user asked for a release; for test builds,
limit `--workflows` (MCP: `workflows`) or ask first. Check with `list_deliveries` (MCP) or
`bakery deliveries` before a bake. `deliver_batch` with `dry_run` (or `bakery deliver 12 --dry-run`)
shows where each job would land.
Confirm with the user before changing deliveries.

## 10. Report back

After a batch, give the user each workflow's result, the artifact names and versions, the
signing check (✓ keystore NAME in `bakery status`), what was delivered where, and the batch
link. Mention anything BuildBakery was missing, and whether you used MCP or the CLI.
