Navigation
Getting Started
Guides
Integrations
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
- API key -- generate one from Settings > API Keys in the dashboard.
- Store as a secret -- add it as
YORKER_API_KEYin your CI provider's secret store. - Config committed -- your
yorker.config.yamland anymonitors/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 === '@' ? '@' : `\\${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 deployHow it works
| Trigger | Job | What it does |
|---|---|---|
| Push or PR touching config files | validate | Validates YAML syntax and schema. Blocks the pipeline on errors. |
| Same-repo pull request | diff | Runs 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 files | deploy | Applies 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_KEYAdd 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).
| Code | Meaning |
|---|---|
0 | Success |
1 | General error (validation failure, API error, missing config) |
2 | Authentication failure |
3 | Plan/quota limit exceeded |
4 | Partial 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 --pruneOnly 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 deployMultiple 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 deployEach API key is scoped to a team, so the same config deploys to different teams.