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
docker pull ghcr.io/xhrdev/xhrdev:latestIf 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
Option A — volume mount (recommended)
Create a directory containing both licence files:
licence/
├── licence.json
└── licence.sigThen mount it into the container:
docker run -d \
--name xhrdev \
--restart unless-stopped \
-p 3000:3000 \
-v ./licence:/run/licence:ro \
ghcr.io/xhrdev/xhrdev:latestOption B — environment variables
Useful for AWS Secrets Manager, Kubernetes Secrets, or any setup where a mounted volume is inconvenient:
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:latestOption C — Docker Compose
services:
xhrdev:
image: ghcr.io/xhrdev/xhrdev:latest
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./licence:/run/licence:rodocker compose up -dHealth check
curl http://localhost:3000/hc
# {"status":"ok"}The image also declares a Docker-native HEALTHCHECK — docker ps shows (healthy) once the solver is ready to accept requests.
Environment variables
| Variable | Default | Description |
|---|---|---|
LICENCE_JSON_CONTENT | — | Licence JSON inline (alternative to volume mount) |
LICENCE_SIG_BASE64 | — | Licence signature, base64-encoded (alternative to volume mount) |
MITM_PROXY_URL | unset | Optional upstream proxy used by internal solver requests |
TIME_SOURCE_PRIORITY, REQUIRE_TIME_SOURCE, ALLOW_SYSTEM_CLOCK_FALLBACK | see below | ECS 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:
docker restart xhrdevHow 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:
- 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.
- At container startup, the launcher verifies the licence signature against that public key, checks the
issued_at/expires_atwindow, decrypts the bundle, and hands it to Node. - A tampered licence or payload fails closed — the process exits with a generic
launcher_failedevent 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:
-e TIME_SOURCE_PRIORITY=ecs \
-e REQUIRE_TIME_SOURCE=ecs \
-e ALLOW_SYSTEM_CLOCK_FALLBACK=falseIf 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.