Guides

CI/CD Integration

How to validate, diff, and deploy monitors automatically with GitHub Actions or GitLab CI.

CI/CD Integration

Wire yorker validate, yorker diff, and yorker deploy into your CI pipeline to get config validation on every push, change previews on pull requests, and automatic deploys on merge.

Prerequisites

  1. API key -- generate one from Settings > API Keys in the dashboard.
  2. Store as a secret -- add it as YORKER_API_KEY in your CI provider's secret store.
  3. Config committed -- your yorker.config.yaml and any monitors/ script files must be in version control.

GitHub Actions

Create .github/workflows/yorker.yml in your repository:

name: Yorker Monitoring as Code

on:
  push:
    paths:
      - "yorker.config.yaml"
      - "monitors/**"
  pull_request:
    paths:
      - "yorker.config.yaml"
      - "monitors/**"

env:
  YORKER_API_KEY: ${{ secrets.YORKER_API_KEY }}
  # Add YORKER_SECRET_* vars here if your config uses {{secrets.*}} interpolation

jobs:
  validate:
    name: Validate config
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install -g @yorker/cli
      - run: yorker validate

  diff:
    name: Preview changes
    if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
    needs: validate
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install -g @yorker/cli
      - name: Run diff
        id: diff
        run: |
          set +e
          OUTPUT=$(yorker diff --json 2>/dev/null)
          EXIT_CODE=$?
          set -e
          EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 2>/dev/null | base64)
          echo "json<<$EOF_MARKER" >> "$GITHUB_OUTPUT"
          echo "$OUTPUT" >> "$GITHUB_OUTPUT"
          echo "$EOF_MARKER" >> "$GITHUB_OUTPUT"
          echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
      - name: Comment on PR
        if: always()
        uses: actions/github-script@v7
        env:
          DIFF_JSON: ${{ steps.diff.outputs.json }}
        with:
          script: |
            const esc = (s) => s.replace(/[|\\`*_{}[\]<>()#+\-!~@\n\r]/g, (ch) => ch === '\n' || ch === '\r' ? ' ' : ch === '@' ? '&#64;' : `\\${ch}`);
            const raw = process.env.DIFF_JSON ?? '';
            let body;
            try {
              const result = JSON.parse(raw);
              if (!result.ok) {
                body = `### Yorker Diff\n\n:x: Error: ${result.error?.message ?? 'Unknown error'}`;
              } else {
                const changes = result.data?.changes ?? [];
                const actionable = changes.filter(c => c.type !== 'unchanged');
                if (actionable.length === 0) {
                  body = '### Yorker Diff\n\n:white_check_mark: No changes. Remote state matches local config.';
                } else {
                  const symbols = { create: '+', update: '~', delete: '-' };
                  const rows = actionable
                    .map(c => `| ${symbols[c.type] ?? '?'} ${c.type} | ${esc(c.kind)} | ${esc(c.name)} |`)
                    .join('\n');
                  body = `### Yorker Diff\n\n| Action | Type | Name |\n|---|---|---|\n${rows}\n\n${actionable.length} change(s) will be applied on merge.`;
                }
              }
            } catch {
              body = `### Yorker Diff\n\n:warning: Could not parse diff output.\n\n<details><summary>Raw output</summary>\n\n\`\`\`\n${raw}\n\`\`\`\n</details>`;
            }

            const comments = await github.paginate(github.rest.issues.listComments, {
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });
            const existing = comments.find(c =>
              c.user?.type === 'Bot' && c.body?.startsWith('### Yorker Diff')
            );
            if (existing) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: existing.id,
                body,
              });
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body,
              });
            }
      - name: Fail on diff errors
        if: steps.diff.outputs.exit_code != '0'
        run: exit ${{ steps.diff.outputs.exit_code }}

  deploy:
    name: Deploy monitors
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install -g @yorker/cli
      - run: yorker deploy

How it works

TriggerJobWhat it does
Push or PR touching config filesvalidateValidates YAML syntax and schema. Blocks the pipeline on errors.
Same-repo pull requestdiffRuns yorker diff --json, parses the output, and posts a summary comment on the PR. Updates the same comment on subsequent pushes. Skipped for fork PRs.
Push to main touching config filesdeployApplies changes to the remote state. Runs after validation passes.

Note: The diff job's if: condition skips fork PRs, where GITHUB_TOKEN is read-only and repository secrets are not exposed. The validate job still runs on fork PRs, but will fail if your config uses {{secrets.*}} placeholders (since the corresponding environment variables won't be set). If you accept fork contributions, either avoid secret placeholders in validation-critical fields or add the same full_name == github.repository guard to the validate job.

Secrets

The workflow uses workflow-level env: so all jobs (including validate) can resolve {{secrets.*}} and {{env.*}} placeholders. Add secrets referenced in your config as additional environment variables:

env:
  YORKER_API_KEY: ${{ secrets.YORKER_API_KEY }}
  YORKER_SECRET_SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  YORKER_SECRET_AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}

GitLab CI

Create .gitlab-ci.yml in your repository:

stages:
  - validate
  - diff
  - deploy

.yorker:
  image: node:20-slim
  before_script:
    - npm install -g @yorker/cli

validate:
  extends: .yorker
  stage: validate
  rules:
    - changes:
        - yorker.config.yaml
        - monitors/**
  script:
    - yorker validate

diff:
  extends: .yorker
  stage: diff
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - yorker.config.yaml
        - monitors/**
  script:
    - yorker diff
  variables:
    YORKER_API_KEY: $YORKER_API_KEY

deploy:
  extends: .yorker
  stage: deploy
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      changes:
        - yorker.config.yaml
        - monitors/**
  script:
    - yorker deploy
  variables:
    YORKER_API_KEY: $YORKER_API_KEY

Add YORKER_API_KEY to your project's Settings > CI/CD > Variables as a masked variable. Only mark it as protected if you restrict it to protected branches — otherwise MR pipelines on unprotected branches won't have access.


JSON output format

Most CLI commands support --json for machine-readable output and share a consistent envelope. The interactive yorker dashboard command does not emit this envelope. The other exception is yorker results tail --json, which emits one JSON object per result (newline-delimited) instead of a single envelope — this allows streaming consumption.

Success

{
  "ok": true,
  "data": { ... }
}

Error

{
  "ok": false,
  "error": {
    "code": "general_error",
    "message": "3 config error(s): ..."
  }
}

Exit codes

These are the codes most relevant to CI pipelines (validate, diff, deploy). Other commands may use additional codes (e.g., yorker status exits 10 when monitors are unhealthy).

CodeMeaning
0Success
1General error (validation failure, API error, missing config)
2Authentication failure
3Plan/quota limit exceeded
4Partial failure (some deploy operations succeeded, others failed)

Key command outputs

yorker validate --json

{
  "ok": true,
  "data": {
    "valid": true,
    "monitors": 5,
    "slos": 2
  }
}

yorker diff --json

{
  "ok": true,
  "data": {
    "changes": [
      {
        "type": "create",
        "kind": "check",
        "name": "API Health",
        "fieldChanges": []
      },
      {
        "type": "update",
        "kind": "check",
        "name": "Homepage",
        "fieldChanges": [
          { "path": "configJson.timeoutMs", "oldValue": 30000, "newValue": 15000 }
        ]
      },
      {
        "type": "unchanged",
        "kind": "check",
        "name": "Orders API",
        "fieldChanges": []
      }
    ]
  }
}

Each change has a type (create, update, delete, unchanged), a kind (check, alert, slo, channel), and a fieldChanges array (empty when there are no field-level differences). Actual CLI output also includes metadata fields such as remoteId, local, and remote which are omitted here for brevity.

yorker deploy --json

Same as diff, plus an applied field with operation counts:

{
  "ok": true,
  "data": {
    "changes": [ ... ],
    "applied": {
      "created": 1,
      "updated": 1,
      "deleted": 0,
      "errors": []
    }
  }
}

If applied.errors is non-empty, the exit code is 4 (partial failure).


Tips

Pin the CLI version

The CLI install takes 2-3 seconds. To lock a specific version:

npm install -g @yorker/[email protected]

Deploy with pruning

To keep remote state exactly in sync (deleting monitors removed from config):

yorker deploy --prune

Only use this if your config is the single source of truth. Monitors created through the web UI will be deleted.

Gate deploys on diff

To require an explicit approval step before deploying, separate the diff and deploy jobs and add a manual gate:

# GitLab CI
deploy:
  stage: deploy
  when: manual
  script:
    - yorker deploy

Multiple environments

Use environment variables to deploy different configs to different environments:

# GitHub Actions
deploy-staging:
  env:
    YORKER_API_KEY: ${{ secrets.YORKER_API_KEY_STAGING }}
  steps:
    - run: yorker deploy

deploy-production:
  env:
    YORKER_API_KEY: ${{ secrets.YORKER_API_KEY_PRODUCTION }}
  needs: [deploy-staging]
  steps:
    - run: yorker deploy

Each API key is scoped to a team, so the same config deploys to different teams.