Skip to content

Deployment

xhr.dev ships as a single Docker image. It runs entirely inside your infrastructure — there is no call back to xhr.dev at runtime, and no traffic, proxy credentials, or target-site data ever leaves your environment.

Multi-platform. The image supports both ARM64 (Apple Silicon, ARM Linux) and x86-64 hosts. Docker pulls the correct architecture automatically.

Prerequisites

You'll receive two files from xhr.dev before deploying:

  • licence.json — your licence (customer ID, enabled features, validity window)
  • licence.sig — an Ed25519 signature over the licence

The container verifies both locally, at startup — there is no network call to an xhr.dev licence server. If the licence is missing, expired, or tampered with, the container exits immediately with {"event":"launcher_failed"} and a non-zero exit code. When your licence nears expiry, xhr.dev sends updated licence.json / licence.sig files — no image rebuild or redeploy of anything but those two files is needed.

Don't have a licence yet? Contact us or book a call.

Pulling the image

bash
docker pull ghcr.io/xhrdev/xhrdev:latest

If you were onboarded with a dedicated registry namespace, pull that instead (e.g. ghcr.io/xhrdev-<your-org>/xhrdev:latest) — xhr.dev will tell you which one to use.

Running

Create a directory containing both licence files:

licence/
├── licence.json
└── licence.sig

Then mount it into the container:

bash
docker run -d \
  --name xhrdev \
  --restart unless-stopped \
  -p 3000:3000 \
  -v ./licence:/run/licence:ro \
  ghcr.io/xhrdev/xhrdev:latest

Option B — environment variables

Useful for AWS Secrets Manager, Kubernetes Secrets, or any setup where a mounted volume is inconvenient:

bash
docker run -d \
  --name xhrdev \
  --restart unless-stopped \
  -p 3000:3000 \
  -e LICENCE_JSON_CONTENT="$(cat licence.json)" \
  -e LICENCE_SIG_BASE64="$(base64 licence.sig)" \
  ghcr.io/xhrdev/xhrdev:latest

Option C — Docker Compose

yaml
services:
  xhrdev:
    image: ghcr.io/xhrdev/xhrdev:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - ./licence:/run/licence:ro
bash
docker compose up -d

Health check

bash
curl http://localhost:3000/hc
# {"status":"ok"}

The image also declares a Docker-native HEALTHCHECKdocker ps shows (healthy) once the solver is ready to accept requests.

Environment variables

VariableDefaultDescription
LICENCE_JSON_CONTENTLicence JSON inline (alternative to volume mount)
LICENCE_SIG_BASE64Licence signature, base64-encoded (alternative to volume mount)
MITM_PROXY_URLunsetOptional upstream proxy used by internal solver requests
TIME_SOURCE_PRIORITY, REQUIRE_TIME_SOURCE, ALLOW_SYSTEM_CLOCK_FALLBACKsee belowECS licence-expiry hardening — see Production notes

The API itself requires no proxy configuration to be set at the container level — proxies are passed per-request (proxy / proxy_url fields in the Akamai and DataDome APIs), so different requests can use different upstream proxies against the same running container.

Renewing a licence

Licences have an expiry window. Before it lapses, xhr.dev sends you a new licence.json / licence.sig pair. Replace the files in your mounted ./licence directory (or update the LICENCE_JSON_CONTENT / LICENCE_SIG_BASE64 secret values) and restart the container — no image pull or rebuild required:

bash
docker restart xhrdev

How the image is protected

The image bundles xhr.dev's solver logic in encrypted form and only decrypts it in memory after a valid licence is verified. Roughly:

  1. At build time, the API and sandbox code is bundled and AES-256-GCM encrypted; the decryption key is compiled into a small Rust launcher binary alongside xhr.dev's Ed25519 public key.
  2. At container startup, the launcher verifies the licence signature against that public key, checks the issued_at / expires_at window, decrypts the bundle, and hands it to Node.
  3. A tampered licence or payload fails closed — the process exits with a generic launcher_failed event rather than partially starting.

This means the same image works for any customer; only the licence file changes. See Security for more on the trust model.

Production notes

Hardening licence-expiry checks on ECS

Licence expiry checks trust your container's clock by default. On AWS/ECS deployments, xhr.dev recommends pinning that check to ECS task metadata instead:

bash
-e TIME_SOURCE_PRIORITY=ecs \
-e REQUIRE_TIME_SOURCE=ecs \
-e ALLOW_SYSTEM_CLOCK_FALLBACK=false

If you're not on ECS, leave these unset — the defaults are appropriate for most deployments. Contact us if you're deploying somewhere else and want an equivalent hardened setup.

Licence delivery via secrets manager

Set LICENCE_JSON_CONTENT and LICENCE_SIG_BASE64; the launcher falls back to the mounted file paths when those env vars are absent.