---
title: 'Deploy with CLI'
description: 'How to deploy monitors, alerts, and SLOs from yorker.config.yaml using the Yorker CLI.'
section: 'Guides'
canonical_url: 'https://yorkermonitoring.com/docs/guides/monitoring-as-code'
---

# Deploy with CLI

The Yorker CLI lets you define monitors, alerts, notification channels, and SLOs in a `yorker.config.yaml` file and deploy them with a single command. Changes are computed as a diff against the current remote state and applied in the correct order.

## Install

```bash
npm install -g @yorker/cli
```

## Authenticate

If you are running this on a workstation, run `yorker login` instead of exporting an API key. The command opens a browser, mints an API key for your team after you click Authorize, and stores it at `~/.yorker/credentials`. Subsequent commands resolve the key automatically.

```bash
yorker login
```

For CI, Docker, and other headless contexts (where `yorker login` cannot launch a browser), generate an API key from **Settings > API Keys** in the dashboard and export it as an environment variable:

```bash
export YORKER_API_KEY=sk_...
```

The CLI also accepts `YORKER_API_URL` to point at a different control plane (defaults to `https://yorkermonitoring.com`).

## Scaffold a config

To create a starter `yorker.config.yaml`, run:

```bash
yorker init
```

The interactive wizard walks you through project name, first monitor URL, type, and frequency.

Pass flags to skip prompts:

```bash
yorker init --name my-app --url https://example.com --type http --frequency 5m
```

## Config file structure

The `yorker.config.yaml` file has these top-level sections:

```yaml
project: my-app              # Required. Project identifier.

alertChannels:                # Notification channel definitions.
  ops-slack:
    type: slack
    webhookUrl: "{{secrets.SLACK_WEBHOOK_URL}}"

defaults:                     # Default settings for all monitors.
  frequency: 5m
  locations:
    - loc_us_east             # Free tier supports 1 location. Add more on paid plans.

groups:                       # Groups of monitors with shared settings.
  - name: API Endpoints
    frequency: 1m
    monitors:
      - name: Users API
        type: http
        url: https://api.example.com/users

monitors:                     # Top-level monitor definitions.
  - name: Homepage
    type: http
    url: https://example.com

slos:                         # Service Level Objectives.
  - name: Homepage Availability
    monitor: Homepage
    target: "99.9%"
    window: 30d

maintenanceWindows:           # Scheduled silences / pauses.
  - name: Weekly DB maintenance
    checks: all
    mode: pause               # pause | continue
    startsAt: "2026-04-12T02:00:00Z"
    endsAt:   "2026-04-12T03:00:00Z"
    recurring: true
    recurrenceRule: "FREQ=WEEKLY;BYDAY=SU"
```

See the [Configuration reference](/docs/reference/configuration#maintenancewindows) for the full `maintenanceWindows` schema.

## Commands

### yorker validate

Validates the config file without deploying. Checks YAML syntax, Zod schema validation, script file existence, secret interpolation, and cross-references (e.g., SLOs referencing valid monitors, alerts referencing valid channels).

```bash
yorker validate
```

Exit code 0 means the config is valid. Non-zero means errors were found — they are printed to stderr.

### yorker diff

Shows what would change between your local config and the remote state without applying anything:

```bash
yorker diff
```

Output shows each resource as CREATE, UPDATE, DELETE, or UNCHANGED, with field-level diffs for updates:

```
Yorker deploy plan for "my-app"

  Checks:
    + CREATE  http  "API Health"  (60s, 2 locations)
    ~ UPDATE  http  "Homepage"
        ~ configJson.timeoutMs  30000 -> 15000
    = UNCHANGED  browser  "Checkout Flow"

  Summary: 1 to create, 1 to update, 1 unchanged

  (dry run — no changes applied)
```

### yorker deploy

Applies changes to the remote state:

```bash
yorker deploy
```

Resources are applied in dependency order: channels first, then checks (with label sync), then alerts, SLOs, and maintenance windows. See the [CLI reference](/docs/reference/cli#deploy-phases) for the full phase table.

Remote resources that exist but are not in your config are reported but **not deleted** unless you pass `--prune`.

### yorker deploy --prune

Deletes remote resources that are not present in the config file:

```bash
yorker deploy --prune
```

This is useful for keeping the remote state in exact sync with the config. Prune deletions happen in the correct dependency order as part of the normal deploy phases — SLOs and alerts are removed early (phases A and B, before their parent checks in phase C), maintenance windows at phase I, and channels at phase Z. See the [CLI reference for `yorker deploy`](/docs/reference/cli#deploy-phases) for the full phase table.

### yorker deploy --force / --accept-remote

If someone edits a YAML-managed resource via the web UI, the next `yorker deploy` detects the change and aborts with a drift report. Two flags control how to resolve:

```bash
yorker deploy --force           # local config wins — overwrite remote
yorker deploy --accept-remote   # skip drifted resources — keep remote as-is
```

See [Drift detection](#drift-detection) below for details.

### yorker status

Displays the current state of all monitors:

```bash
yorker status
```

### yorker results tail

Live-stream check results as they arrive:

```bash
yorker results tail "Homepage" --interval 30s
```

### yorker test

Runs HTTP monitors locally against their configured URLs. Useful for validating URLs and auth before deploying:

```bash
yorker test
```

Browser monitors are listed but not executed locally (use Playwright directly for local browser tests).

## Drift detection

The CLI tracks the state of each deployed resource in `.yorker/.deploy-state.json` (gitignored, per-machine). After every successful deploy, it saves a config hash and the remote `updatedAt` timestamp for each resource. On the next deploy, it compares:

- **Local changed?** — current config hash differs from the stored hash.
- **Remote changed?** — remote `updatedAt` is newer than the stored timestamp, and the resource has `managedBy: "yaml"`.

This produces four possible outcomes:

| | Remote unchanged | Remote changed |
|---|---|---|
| **Local unchanged** | Skip | **Drift** (remote-only edit) |
| **Local changed** | Normal update | **Conflict** (both sides changed) |

When drift or conflicts are detected, the deploy aborts with a report showing which resources were affected. You have three options:

1. **Review and choose** — inspect the remote changes in the dashboard, then update your config to match or intentionally overwrite.
2. **`--force`** — local config wins, remote changes are overwritten.
3. **`--accept-remote`** — drifted resources are skipped, keeping their remote state.

### First deploy

On the first deploy (no state file exists), drift detection is skipped entirely — the CLI creates the state file after a successful apply.

### After `yorker pull`

Every successful `yorker pull` rewrites `.yorker/.deploy-state.json` with a fresh snapshot of remote state, so the next deploy treats everything as a clean baseline with no drift.

### `.yorker/.deploy-state.json`

This file is per-machine state and should not be committed. Add it to your `.gitignore`:

```text
.yorker/.deploy-state.json
```

---

## Secret interpolation

To keep secrets out of your config file, use placeholder syntax. The CLI resolves these at deploy time from environment variables.

### `{{secrets.NAME}}`

Looks up `YORKER_SECRET_NAME` first, then falls back to `NAME`:

```yaml
auth:
  type: bearer
  token: "{{secrets.AUTH_TOKEN}}"
```

Set with `export YORKER_SECRET_AUTH_TOKEN=...` or `export AUTH_TOKEN=...`.

### `{{env.NAME}}`

Looks up the environment variable directly:

```yaml
monitors:
  - name: Staging API
    type: http
    url: "{{env.STAGING_URL}}/health"
```

### `${NAME}`

Legacy shorthand, equivalent to `{{env.NAME}}`. Not applied inside browser script files to avoid conflicts with JavaScript template literals.

The CLI fails with a clear error if any placeholder is unresolved after interpolation.

## CI/CD integration

Validate on push, preview changes on PRs with `yorker diff`, and deploy on merge. See the full [CI/CD Integration guide](/docs/guides/ci-cd) for complete GitHub Actions and GitLab CI workflows.

```yaml
# GitHub Actions — quick start
- run: npm install -g @yorker/cli
- run: yorker validate
- run: yorker diff
- run: yorker deploy --force                    # CI owns the config
  if: github.ref == 'refs/heads/main'
```

Use `--force` in CI pipelines where the config file is the source of truth. If you want CI to preserve manual edits made via the dashboard, use `--accept-remote` instead.

Set `YORKER_API_KEY` and any `YORKER_SECRET_*` variables at the job or workflow level so all steps — including `validate` — can resolve `{{secrets.*}}` placeholders.

## Complete example

```yaml
project: my-app

alertChannels:
  ops-slack:
    type: slack
    webhookUrl: "{{secrets.SLACK_WEBHOOK_URL}}"
  on-call-email:
    type: email
    addresses:
      - oncall@example.com
  pagerduty-oncall:
    type: pagerduty
    routingKey: "{{secrets.PAGERDUTY_ROUTING_KEY}}"

defaults:
  frequency: 5m
  locations:
    - loc_us_east
  http:
    timeoutMs: 15000
    followRedirects: true
    assertions:
      - type: status_code
        value: 200
  browser:
    viewport:
      width: 1280
      height: 720
    screenshotMode: every_step
  alerts:
    - conditions:
        - type: consecutive_failures
          count: 2
      channels:
        - "@ops-slack"

groups:
  - name: Critical APIs
    frequency: 1m
    locations:
      - loc_us_east
      - loc_us_west
      - loc_eu_central
    alerts:
      - conditions:
          - type: consecutive_failures
            count: 1
          - type: multi_location_failure
            minLocations: 2
        channels:
          - "@ops-slack"
          - "@pagerduty-oncall"
    monitors:
      - name: Payments API
        type: http
        url: https://api.example.com/payments
        assertions:
          - type: status_code
            value: 200
          - type: response_time
            max: 1000
      - name: Auth API
        type: http
        url: https://api.example.com/auth/health

monitors:
  - name: Homepage
    type: http
    url: https://example.com
  - name: Checkout Flow
    type: browser
    script: ./monitors/checkout.ts
    frequency: 5m
    alerts:
      - name: checkout-warning
        conditions:
          - type: consecutive_failures
            count: 2
        channels:
          - "@ops-slack"
      - name: checkout-critical
        conditions:
          - type: consecutive_failures
            count: 5
        channels:
          - "@pagerduty-oncall"
          - "@on-call-email"

slos:
  - name: Homepage Availability
    monitor: Homepage
    target: "99.95%"
    window: 30d
    channels:
      - "@ops-slack"
  - name: Checkout Availability
    monitor: Checkout Flow
    target: "99.9%"
    window: 30d
    channels:
      - "@ops-slack"
      - "@pagerduty-oncall"
```
