> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revyl.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Named build profiles, recipe fields, and build CLI flags

`.revyl/config.yaml` owns the build recipes used by both local and remote
builds. `revyl init` writes a detected starting point. Edit the YAML directly,
then run `revyl config validate`.

Direct builds require a project config with named profiles. If the selected file is
legacy config, `revyl build` stops and directs you to preview and run
`revyl config migrate`; it does not translate or rewrite the file implicitly.

Each invocation resolves exactly one named profile and one platform recipe:

```bash theme={null}
revyl build --profile development --platform ios
```

The command runs in the cloud using the recipe's toolchain image and caches.

Cloud builds require authentication, a Git worktree, and a configured `app_id`
UUID for an accessible app of the same platform. Cloud billing and capacity
apply; failures never fall back to local execution.

`revyl dev` remains local unless you pass `--remote`; test/workflow `--build`
continuations also remain local.

For Expo, the native prebuild recipes below run Xcode or Gradle directly and
do not require Expo authentication. If you intentionally replace them with an
EAS local-build command, install EAS CLI and add an encrypted `EXPO_TOKEN`
reference for unattended remote builds.

## A complete example

<Tabs>
  <Tab title="Expo">
    ```yaml theme={null}
    build:
      framework: expo
      env:
        EXPO_PUBLIC_API_URL: https://staging.example.com
      profiles:
        development:
          ios:
            app_id: "<your-ios-app-uuid>"
            setup_commands:
              - bun install
              - bunx expo prebuild --platform ios
              - cd ios && pod install
            build_commands:
              - cd ios && xcodebuild -workspace YourApp.xcworkspace -scheme YourApp -configuration Release -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build ARCHS=arm64
            output_path: ios/build/Build/Products/Release-iphonesimulator/*.app
            image: ios-macos-26-xcode-26.5
          android:
            app_id: "<your-android-app-uuid>"
            setup_commands:
              - bun install
              - bunx expo prebuild --platform android
            build_commands:
              - cd android && ./gradlew assembleRelease
            output_path: android/app/build/outputs/apk/release/*.apk
    ```
  </Tab>

  <Tab title="React Native">
    ```yaml theme={null}
    build:
      framework: react_native
      profiles:
        development:
          ios:
            app_id: "<your-ios-app-uuid>"
            setup_commands:
              - npm install
              - cd ios && pod install
            build_commands:
              - cd ios && xcodebuild -workspace YourApp.xcworkspace -scheme YourApp -configuration Release -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build ARCHS=arm64
            output_path: ios/build/Build/Products/Release-iphonesimulator/*.app
            image: ios-macos-26-xcode-26.5
          android:
            app_id: "<your-android-app-uuid>"
            setup_commands:
              - npm install
            build_commands:
              - cd android && ./gradlew assembleRelease
            output_path: android/app/build/outputs/apk/release/*.apk
    ```
  </Tab>

  <Tab title="iOS">
    ```yaml theme={null}
    build:
      framework: ios
      profiles:
        development:
          ios:
            app_id: "<your-ios-app-uuid>"
            build_commands:
              - xcodebuild -project YourApp.xcodeproj -scheme YourApp -configuration Release -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build ARCHS=arm64
            output_path: build/Build/Products/Release-iphonesimulator/*.app
            image: ios-macos-26-xcode-26.5
    ```
  </Tab>

  <Tab title="Android">
    ```yaml theme={null}
    build:
      framework: android
      profiles:
        development:
          android:
            app_id: "<your-android-app-uuid>"
            build_commands:
              - ./gradlew assembleRelease
            output_path: app/build/outputs/apk/release/*.apk
    ```
  </Tab>
</Tabs>

## Profiles and platform recipes

Profiles name a complete build intent such as `development`, `pull-request`,
or `release`. Each profile may contain an `ios` recipe, an `android` recipe, or
both. There is no active or default profile stored in the file or on the
server.

`revyl build` applies explicit `--profile` and `--platform` values first. When
an omitted value has exactly one eligible choice, the CLI selects it. If
multiple choices remain, an interactive terminal prompts and a non-interactive
invocation fails with the valid choices and the flag to provide.

```bash theme={null}
revyl build --profile development --platform ios
revyl build --profile release --platform android --remote
```

`--platform` accepts only `ios` or `android`; profile names are passed to
`--profile`.

## Build fields

`build.framework` is required when `build` is present and applies to every
profile. Supported values are `ios`, `android`, `react_native`, `expo`, and
`flutter`.

Build-level `env`, `secrets`, and `caches` are inherited by every recipe. A
recipe can add or override them as described below.

| Recipe field | Direct build requirement | Description |
| - | - | - |
| `app_id` | Remote and non-interactive local: yes. Interactive local: optional before execution. | UUID of the Revyl app that receives the finished build. After a successful interactive local build without one, Revyl lets you select or create an app and offers to save the binding into this recipe. |
| `build_commands` | Yes | Ordered shell commands or built-in steps, optionally named. Every declared recipe contains this field. |
| `setup_commands` | No | Ordered preparation commands or built-in steps run before `build_commands`, optionally named. |
| `output_path` | Local: yes. Remote: optional. | Artifact path or glob relative to the selected project root. When absent remotely, Revyl uses the platform's existing default artifact glob. |
| `image` | No | [Toolchain image](/remote-builds/toolchains) used for remote execution. |
| `timeout_seconds` | No | Positive timeout for recipe execution. |
| `env` | No | Non-secret environment values merged over `build.env`. |
| `secrets` | No | Secret reference names merged with `build.secrets`. |
| `caches` | No | Configured path caches merged with `build.caches` by key. See [Caching](/remote-builds/caching). |

The authored contract permits `app_id` and `output_path` to be absent. A local
build-and-upload requires `output_path` before running commands. A remote build
may omit it and use the existing platform artifact-discovery default, but it
requires `app_id` before submission. An interactive local build may run without
`app_id`; after producing its artifact, the existing app-selection flow selects
or creates the destination and offers to save that app ID into the selected
recipe. Non-interactive local builds require the binding before running commands.

All setup and build commands run in order from the project root selected by the
nearest `.revyl/config.yaml` (or by `-C`). Selecting a recipe never rewrites its
commands, publishes project configuration, or registers saved-profile identity
with the ordinary build API. The optional post-build local app binding is the
only config write in this flow; remote builds require the binding up front and
do not select, create, or save an app.

### Named build steps

Add `name` to a command in either list to label it in the remote build's Steps tab:

```yaml theme={null}
setup_commands:
  - name: Install dependencies
    run: npm ci
build_commands:
  - name: Build Android APK
    run: cd android && ./gradlew assembleDebug
```

Names are optional. Plain command strings still work, and built-in steps can
also have a `name`.

## Inheritance

Build-level values are resolved into each recipe deterministically:

* Recipe `env` entries replace build-level values with the same key.
* Effective `secrets` are the ordered, de-duplicated build-level names followed
  by recipe names.
* Caches merge by key. Paths are the ordered, de-duplicated build-level paths
  followed by recipe paths.
* A name cannot be both an effective environment key and a secret reference.

## Artifact resolution

After the last build command exits successfully, a local build resolves its
required `output_path`. A remote build uses its authored `output_path` when
present, or falls back to the existing platform default:

* **iOS:** `build/**/*.app`; the result must be a simulator `.app` bundle or a
  supported authored archive containing one.
* **Android:** `**/build/outputs/apk/**/*.apk`; `.aab` artifacts are unsupported. Use an `.apk`.

Use an explicit path or glob that identifies one artifact. Avoid broad globs
over cached output directories that can also match stale products.

## Environment variables

Non-secret values are exported into the selected recipe's setup and build
commands. Use them for public build configuration such as `EXPO_PUBLIC_*` or
`ENVFILE`:

```yaml theme={null}
build:
  framework: ios
  env:
    NODE_ENV: development
  profiles:
    development:
      ios:
        build_commands: ["./scripts/build-ios"]
        output_path: build/App.app
        env:
          EXPO_PUBLIC_API_URL: https://staging.example.com
```

Override or add values for one remote invocation:

```bash theme={null}
revyl build --profile development --platform ios --remote \
  --env EXPO_PUBLIC_API_URL=https://preview.example.com
```

`--env` is a remote-only flag. Its values take precedence over the inherited
config environment. Local builds use the inherited config environment as
authored. Do not put credentials or tokens in `env` or `--env`; use secret
references instead.

### Compilation cache controls

These reserved environment settings control automatic compilation caching:

| Setting | Platform | Values |
| - | - | - |
| `REVYL_XCODE_COMPILATION_CACHE_ENABLED` | iOS | `"0"` or `"1"` |
| `REVYL_GRADLE_BUILD_CACHE_ENABLED` | Android | `"0"` or `"1"` |
| `REVYL_CCACHE_ENABLED` | Android | `"0"` or `"1"` |

All three default to `"1"`. `revyl build --remote --no-cache` forces them off
for one remote build and also skips configured path caches. See
[Caching](/remote-builds/caching).

Do not put credentials or tokens in `env` or `--env`; those values are part of
the remote build request. Use encrypted build secrets instead.

## Build secrets

Create or update an encrypted organization secret through the masked prompt:

```bash theme={null}
revyl build secret set EXPO_TOKEN
```

For CI, send the value over stdin so it does not appear in process arguments or
shell history:

```bash theme={null}
printf '%s' "$EXPO_TOKEN" | revyl build secret set EXPO_TOKEN --stdin
```

Commit only the reference name:

```yaml theme={null}
build:
  framework: ios
  secrets:
    - EXPO_TOKEN
  profiles:
    development:
      ios:
        build_commands: ["./scripts/build-ios"]
        output_path: build/App.app
        secrets:
          - SENTRY_AUTH_TOKEN
```

Add references for one invocation with repeatable `--secret` flags:

```bash theme={null}
revyl build --profile development --platform ios --remote \
  --secret EXPO_TOKEN --secret SENTRY_AUTH_TOKEN
```

Remote builds validate the names, decrypt the stored values only when the
sandbox starts, and inject them into every setup and build command. Local
builds read the same effective names from the current process environment and
fail before running commands if a referenced value is missing.

Revyl does not parse `.env.local` automatically. Keep it out of version
control. List or delete stored names without revealing their values:

```bash theme={null}
revyl build secret list
revyl build secret delete EXPO_TOKEN
```

## Remote source

`revyl build` archives the active Git worktree, including tracked files with
current edits and nonignored untracked files, and sends the selected project
directory as the runner workdir. This preserves workspace files and shared packages for nested
monorepo projects while commands and artifact paths remain relative to the
project containing `.revyl/config.yaml`. It does not publish the config or send
saved-profile identity. The profile name and platform remain invocation-local
selection inputs.

Deleted tracked files are omitted. Never track secrets, and add private runtime
files to `.gitignore`. Git ignore rules do not exclude files that are already
tracked. Use encrypted organization secrets rather than including their values
in the source or build configuration.

### Speed up uploads for large repositories

For monorepos or large repositories, use `.revylignore` to leave files the build
does not need out of the source archive. This reduces upload size and time:

```gitignore theme={null}
/docs/
/design-assets/
```

Place `.revylignore` in the project root containing `.revyl/config.yaml`, or at
the Git worktree root. The project file takes precedence when both exist.
Patterns with a leading `/` are relative to the Git worktree root, even for a
nested project. For example, use `/apps/mobile/docs/` for `apps/mobile/docs/`.

## CLI reference

```bash theme={null}
revyl build [--profile <name>] [--platform ios|android] [flags]
```

| Flag | Description |
| - | - |
| `--profile <name>` | Select a named local profile. Required when profile selection remains ambiguous. |
| `--platform ios\|android` | Select the recipe inside the profile. Required when platform selection remains ambiguous. |
| `--local` | Build on your machine and upload the result. |
| `--remote` | Run on a Revyl cloud runner, the default. Retained for compatibility; `--remote=false` selects local execution. |
| `--image <key>` | Override the selected recipe's remote toolchain image. |
| `--env KEY=VALUE` | Remote environment override, repeatable. |
| `--secret NAME` | Secret reference, repeatable. Local builds read it from the process environment; remote builds use the stored organization secret. |
| `--timeout <seconds>` | Override the selected recipe's remote timeout. |
| `--version <string>` | Version label for the registered build. Auto-generated when omitted. |
| `--detach` | Queue a remote build and return the job ID without waiting. |
| `--no-cache` | Run a cold remote build without compilation caches or configured path caches. |
| `--no-set-current` | Do not make the new version the app's current build. |
| `--json` | Write the remote-build JSON envelope by default. `--local --json` preserves the local build/upload JSON format. |
| `--debug` | Stream detailed diagnostics and remote build logs. |

Do not combine `--local` with `--remote`. `--image`, `--env`, `--timeout`,
`--detach`, and `--no-cache` are cloud-only.

Managing running remote builds:

```bash theme={null}
revyl build status <build-job-id>
revyl build status <build-job-id> --follow
revyl build cancel <build-job-id>
revyl build list
```

## Timeout

Remote builds are stopped by the server when they exceed their timeout: 60
minutes by default, up to a maximum of four hours. Set a recipe-specific value:

```yaml theme={null}
build:
  framework: ios
  profiles:
    release:
      ios:
        build_commands: ["./scripts/build-release"]
        output_path: build/Release.app
        timeout_seconds: 3600
```

Or override it for one invocation:

```bash theme={null}
revyl build --profile release --platform ios --remote --timeout 3600
```

Ctrl-C stops following the build; the build keeps running in the cloud. Cancel
it with `revyl build cancel <build-job-id>`.

## Related

* [Caching](/remote-builds/caching): compilation and configured path caches
* [Toolchain Images](/remote-builds/toolchains): available images and installed tools
* [Artifact Requirements](/builds/artifact-requirements): supported `.app` and `.apk` artifacts


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.