---
title: 'Private Locations'
description: 'Deploy Yorker runners on your own infrastructure to monitor internal services, staging environments, and VPNs.'
section: 'Guides'
canonical_url: 'https://yorkermonitoring.com/docs/guides/private-locations'
---

# Private Locations

A private location runs the Yorker check runner on your own infrastructure instead of Yorker's hosted fleet. Use it to monitor internal services, staging environments, or anything behind a VPN -- from inside your own network. The same monitors, alerting, and dashboards work identically, with one exception: [scripted browser monitors run on hosted locations only](#monitors-an-agent-cannot-run).

Private location runs are billed at 50% of hosted runs because you provide the compute.

## Prerequisites

- A Yorker plan that includes private locations (free tier: 0; paid: up to 2; enterprise: unlimited)
- Docker installed on the target machine
- Outbound HTTPS access from the agent machine to `yorkermonitoring.com:443` -- no inbound connections are required

---

## Step 1: Create a Location

To create a location via the CLI, provide a unique slug (`--name`) and a human-readable label (`--display-name`):

```bash
yorker locations create --name hq-data-center --display-name "HQ Data Center"
```

The CLI also auto-generates a runner key. Use the key and location ID from the output when continuing with Step 3 below.

To create a location via the dashboard: go to **Settings > Locations** and click **Add Location**.

Both paths return a location ID in the form `loc_...`. Save it -- you will need it in the next step.

---

## Step 2: Generate a Runner Key

Runner keys authenticate the agent to the control plane. Each key is shown only once.

To generate a key via the CLI:

```bash
yorker locations keys create <location-id>
```

To generate a key via the dashboard: expand the location row, enter a key name, and click **Create Key**.

Copy the key immediately -- it cannot be retrieved after the page is closed.

---

## Step 3: Deploy the Agent

The agent is published as a Docker image. Download the Compose file and the environment template into an empty directory on the machine that will run it:

```bash
curl -fsSLO https://yorkermonitoring.com/agent/docker-compose.yml
curl -fsSL -o .env https://yorkermonitoring.com/agent/agent.env.example
```

The template pre-fills `RUNNER_MODE`, `CONTROL_PLANE_URL`, and `LOCATION_TYPE` with their required values. Open `.env` and fill in the four values specific to your deployment:

```bash
RUNNER_API_KEY=rk_...          # The runner key from Step 2
LOCATION_ID=loc_...            # The location ID from Step 1
TEAM_ID=team_...               # Included in the `yorker locations create` CLI output
LOCATION_NAME=HQ Data Center   # Human-readable label for logs and telemetry
```

The runner key is a credential. Keeping it in `.env` keeps it out of your shell history and the process list, which a one-line `docker run -e RUNNER_API_KEY=...` does not. Restrict the file to your own user with `chmod 600 .env`.

The dashboard and the CLI also print a `docker run` command when you create a runner key, for trying an agent quickly without Compose. It comes in two steps: the first reads the key into your shell without showing it, and the second starts the agent, so the key is not written into the command. The image in that command is the current release, named by version and by digest (`yorker-agent:1.0.2@sha256:...`) instead of `latest`, so Docker only runs the exact image that was released.

Then start the agent:

```bash
docker compose up -d
```

### Choosing an image

Two images are published, for `linux/amd64` and `linux/arm64`:

| Image | Runs | Size |
|---|---|---|
| `ghcr.io/reverse-sweep-ltd/yorker-agent` | HTTP, MCP, and browser monitors (includes Playwright + Chromium) | ~550 MB download, ~2 GB on disk |
| `ghcr.io/reverse-sweep-ltd/yorker-agent-light` | HTTP and MCP monitors only | ~130 MB download, ~570 MB on disk |

Both images run as an unprivileged user, and the Compose file drops all Linux capabilities and sets `no-new-privileges`. The agent needs no inbound ports, no volumes, and no elevated permissions.

The Compose file runs `yorker-agent` by default. To run the lighter image, add this line to `.env` and run `docker compose up -d` again:

```bash
YORKER_AGENT_IMAGE=yorker-agent-light
```

### Screenshots

Browser monitors on a private location capture the same screenshots as hosted ones. The agent sends each screenshot to Yorker over the outbound HTTPS connection it already uses for results, and they appear in the monitor's filmstrip. There is nothing to configure, and no extra host to allow through a firewall. Each screenshot can be up to 4 MB, and a location can send up to 12,000 an hour, which is far more than its monitors produce in normal use.

This needs agent 1.0.2 or later. An older agent runs browser monitors but its screenshots never reach Yorker; [update it](#updating-the-agent).

Screenshots are images of the pages the agent loads, so with the default setup those images leave your network and are stored by Yorker, with the same retention as hosted screenshots. If they must stay in storage you control, give the agent an S3-compatible bucket in `.env`:

```bash
ARTIFACT_S3_ENDPOINT=https://s3.eu-west-2.amazonaws.com
ARTIFACT_S3_BUCKET=synthetics-artifacts
ARTIFACT_S3_ACCESS_KEY_ID=...
ARTIFACT_S3_SECRET_ACCESS_KEY=...
ARTIFACT_S3_REGION=eu-west-2            # "auto" for Cloudflare R2 and MinIO
ARTIFACT_PUBLIC_URL=                    # optional: base URL put in front of each object key in telemetry
```

The agent then uploads screenshots to that bucket instead of sending them to Yorker. Yorker cannot read your bucket, so the filmstrip in the dashboard stays empty for that location. The screenshot links are included in the OTLP telemetry the agent emits, so you can open them from your own backend.

### Direct OTLP export

The agent can export metrics, traces, and logs straight to your own OTel-compatible backend, entirely from inside your network (this connection never transits Yorker). Set these in `.env`:

```bash
OTLP_ENDPOINT=https://api.honeycomb.io
OTLP_API_KEY=                              # sent verbatim as "Authorization" (leave unset if not needed)
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-team-key,x-honeycomb-dataset=yorker
```

`OTEL_EXPORTER_OTLP_HEADERS` follows the [standard OTel env var format](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#specifying-headers-via-environment-variables): comma-separated `key=value` pairs, values URL-encoded. Use it for backends that need a header other than `Authorization` (Honeycomb, Datadog's `dd-api-key`, New Relic's `api-key`, SigNoz Cloud's `signoz-ingestion-key`) or more than one header. If a header name here collides with `OTLP_API_KEY`'s `Authorization`, the entry in `OTEL_EXPORTER_OTLP_HEADERS` wins.

---

## Step 4: Verify Health

After the agent starts, it begins polling the control plane. To verify it is connected:

- **Dashboard:** the location's status badge turns green (Active) in **Settings > Locations**.
- **CLI:** run `yorker locations list` and check the status column.

Health states are derived from the time since the last successful poll:

| Status | Meaning |
|---|---|
| Active | Last poll within the past 5 minutes |
| Degraded | Last poll 5--10 minutes ago |
| Offline | Last poll more than 10 minutes ago, or never connected |

If the agent does not appear Active within a few minutes, see [Troubleshooting](#troubleshooting) below.

---

## Step 5: Assign the Location to a Monitor

Monitors only execute in the locations they are assigned to. A monitor can be assigned to any mix of hosted and private locations.

To assign via the dashboard: edit a monitor and open the location picker. Your private location appears under the **Private** group with its current health badge.

To assign via `yorker.config.yaml`, add the location ID to the monitor's `locations` array:

```yaml
monitors:
  - name: Internal API
    type: http
    url: https://api.internal.example.com/health
    locations:
      - loc_us_east          # hosted location
      - loc_abcdef123456     # your private location
```

Deploy the config to apply:

```bash
yorker deploy
```

---

## Updating the Agent

Hosted locations pick up runner changes automatically. A private agent runs on your infrastructure, so it stays on the image it was started with until you update it. An agent that falls behind keeps running the monitors it understands, but it cannot run monitor types or settings that were added after its release.

To move to the newest release:

```bash
docker compose pull
docker compose up -d
```

The agent restarts in a few seconds and resumes polling. Monitors assigned to hosted locations are unaffected while it restarts.

If you started the agent with the `docker run` command from the dashboard or the CLI instead of Compose, that command names a specific release, so running it again starts the same version. To upgrade:

1. Create a new runner key for the location, in the dashboard or with `yorker locations keys create <location-id>`. It comes with the start command for the current release.
2. Remove the old container:

   ```bash
   docker rm -f yorker-agent-loc_abcdef123456   # your location ID
   ```

3. Run the two commands shown with the new key.
4. Revoke the old key.

If you pinned a release with `YORKER_AGENT_VERSION`, change the pin first; `docker compose pull` on its own fetches the pinned version again.

### Choosing how updates arrive

By default the Compose file tracks the `latest` tag, so every `docker compose pull` fetches the newest release. Two common setups:

- **Follow releases automatically.** Keep `latest` and run the two commands above on a schedule, for example a weekly cron job.
- **Control each upgrade.** Pin a release in `.env` and change the line when you are ready to upgrade:

  ```bash
  YORKER_AGENT_VERSION=1.0.1
  ```

Released version tags are never overwritten, so a pinned agent runs the same image until you change the pin.

### Checking what is running

The agent reports its version to Yorker each time it polls. **Settings > Locations** and `yorker locations list` show it in the **Agent** column, flagged **Update available** when a newer release exists. An agent started from a build that predates published releases shows **Unknown version**; update it the same way.

The agent also serves its version, build, and what it can run on the health port:

```bash
docker compose exec yorker-agent curl -s http://localhost:8080/version
```

### Monitors an agent cannot run

Some monitors cannot run on a given agent. When one is assigned to a private location, the location is flagged in **Settings > Locations** (expand the row for the list) and in `yorker locations list`, with the reason:

| Monitor | Runs on |
|---|---|
| HTTP, MCP | `yorker-agent` and `yorker-agent-light` |
| Browser (URL mode) | `yorker-agent` only |
| Browser (scripted) | Hosted locations only |

A monitor that cannot run on a private location still runs on its other locations.

---

## Network Requirements

The agent only needs outbound connectivity -- no inbound connections are required and no firewall rules need to be opened toward Yorker.

| Requirement | Detail |
|---|---|
| Outbound HTTPS | `yorkermonitoring.com:443` -- for polling checks and submitting results and screenshots |
| Outbound HTTPS (install and update only) | `ghcr.io:443` and `pkg-containers.githubusercontent.com:443` -- for pulling the agent image |
| Internal network access | The agent must be able to reach the services it monitors (e.g., internal hostnames, VPN resources) |

There is no VPN tunnel from the agent to Yorker's infrastructure. All communication is initiated by the agent over standard HTTPS.

---

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| Location stays Offline | Wrong runner key or location ID | Verify `RUNNER_API_KEY` and `LOCATION_ID` in `.env` match what was generated in Steps 1 and 2 |
| Location stays Offline | Firewall blocking outbound HTTPS | Allow outbound connections from the agent machine to `yorkermonitoring.com:443` |
| Location is Active but no checks run | Wrong `TEAM_ID` | The runner polls with team scoping -- verify `TEAM_ID` matches your team in Settings |
| Checks not executing | Agent is connected but no checks assigned | Assign the private location to at least one monitor (Step 5) |
| `docker compose pull` fails | Registry blocked by an egress firewall | Allow outbound HTTPS to `ghcr.io` and `pkg-containers.githubusercontent.com` |
| Browser checks fail | Using the light image | Remove `YORKER_AGENT_IMAGE=yorker-agent-light` from `.env` to run `yorker-agent`, which includes Playwright and Chromium |
| Browser monitors run but show no screenshots | Agent is older than 1.0.2 | [Update the agent](#updating-the-agent) |
| Browser monitors run but show no screenshots | `ARTIFACT_S3_*` is set, so screenshots go to your own bucket | Expected. Remove the `ARTIFACT_S3_*` lines from `.env` to have them sent to Yorker and shown in the filmstrip; see [Screenshots](#screenshots) |
| A new monitor type or setting does not work | Agent is older than the feature | [Update the agent](#updating-the-agent) |
