Back to README | Roadmap | Architecture
Every command, route and environment variable on this page was checked against src/arc_runner/cli/main.py, src/arc_runner/server/ and src/arc_runner/core/path_policy.py. Nothing here has been run on a published artifact: no release has been tagged and neither the PyPI package nor a container image exists yet (see section 7).
Two deployment targets are prepared:
- Python wheel (
arc_runner-<version>-py3-none-any.whl): console commandsarc-runnerandintel-arc-models-runner(same entry point). The Level Zero loader is found at run time (ze_loader.dllon Windows,libze_loader.so.1on Linux). - Docker image (
ubuntu:24.04base, multi-stage, non-rootappuserwith UID 1000): installs the Intel compute runtime packages from the Intel apt repository and expects/dev/drito be passed through for GPU access.
Both are built from this repository. Neither is published; build them locally (sections 2.1 and 3.1).
# From a checkout (works today)
pip install .
# Build the wheel and sdist locally
python scripts/build_wheel.py # writes dist/
pip install dist/arc_runner-<version>-py3-none-any.whl
# From PyPI: only after the first release is published (not the case yet)
pip install arc-runnerarc-runner --version
arc-runner devices # lists CPU, GPU and NPU found by the detector; add --json for JSON
arc-runner serve --host 127.0.0.1 --port 11434arc-runner serve defaults to 127.0.0.1:11434. Useful options: --model-dir <dir> (repeatable), --default-model <name>, --debug (enables /docs), --log-file <path>. Other subcommands: pull, models, rm, benchmark, inspect-gguf, import-ollama, aot-compile, embed, convert, adapter, batch; run arc-runner --help for the full list.
| Route | Purpose |
|---|---|
GET / |
Liveness probe: plain text Ollama is running (the HTML UI when the client asks for text/html) |
GET /ui |
Web UI |
GET /api/version, /api/tags, POST /api/show, /api/generate, /api/chat, /api/pull, DELETE /api/delete, POST /api/copy |
Ollama-compatible API |
GET /v1/models, GET /v1/models/{model}, POST /v1/chat/completions |
OpenAI-compatible API |
POST /v1/studio/... |
Multimodal studio endpoints |
GET /api/devices, /api/batch/status, POST /api/batch/preempt, /api/adapters/* |
Devices, batch and LoRA adapters |
There is no /v1/health route; use GET / for health checks.
docker-compose.yml builds the image from the Dockerfile (tag intel-arc-models-runner:local), passes /dev/dri through, mounts two cache volumes and ./models read-only at /models, and health-checks GET /.
docker compose up -d --build
docker compose logs -f arc-runner
docker compose psThe server listens on http://localhost:8000 (the image command is arc-runner serve --host 0.0.0.0 --port 8000). Because it binds to all interfaces inside the container, publish the port only to networks you trust, or set ARC_API_KEY (section 5).
After a release image exists, use a version tag, not latest:
docker run -d \
--name intel-arc-models-runner \
--restart unless-stopped \
--device /dev/dri:/dev/dri \
-p 8000:8000 \
-e ARC_API_KEY="$ARC_API_KEY" \
-v arc-models-cache:/home/appuser/.cache/arc-runner/models \
-v arc-aot-cache:/home/appuser/.cache/arc-runner/aot \
ghcr.io/techdeveloper-org/intel-arc-models-runner:<version>--device /dev/dri:/dev/dri gives the container the render nodes. Without /dev/dri the entrypoint prints a notice and the runner uses the CPU. Nothing about GPU performance in the container has been measured here.
The docker-build workflow pushes images only when a GitHub release for a tag vX.Y.Z is published (the tag must equal the VERSION file). The tags are the version number and a short commit SHA; latest is added only for a release tag without a pre-release suffix. Pushes to main and manual runs build the image without pushing it.
The base image is pinned by tag (ubuntu:24.04). Pinning its digest is a maintainer step before a release (see the comment at the top of the Dockerfile).
The package works without the native library (pure-Python path). To use it, build it on the target machine with GCC (MinGW-w64 on Windows):
python src/arc_runner/native/build_native.pyThe build writes libarc_native_sys.<dll|so> and a .build.json sidecar (sha256, compiler, flags, ABI version). The Python bridge refuses a library with another ABI version or a different hash and falls back to the Python path. The Linux container image and the published wheel contain no Linux .so, so they run the Python path unless one is built into the image. Related variables: ARC_NATIVE_DISABLE=1 forces the fallback, ARC_ZE_LOADER_PATH overrides the Level Zero loader, ARC_ZE_SYNC_TIMEOUT_MS sets the GPU wait limit (clamped to 1900), ARC_PIN_RING=1 enables best-effort layer pinning, ARC_KV_CACHE_PRECISION=u8 opts in to the u8 KV cache (not recommended: on the Arc GPU with default settings the one committed quality run, docs/benchmarks/quality/kv_u8_qwen-2.5-1.5b.json, failed with a paged-attention block-size error and 0 passing prompts).
Further variables, as the code reads them:
| Variable | Effect |
|---|---|
ARC_CONTEXT_WINDOW |
Fixes the context window for every loaded model. Only ASCII digits, at most 7 characters (leading zeros count, so 00000008 is rejected), value 1 to 2097152. Unset or empty: max_position_embeddings from the model's config.json, then 4096. An invalid value makes arc-runner serve print one line on stderr and exit with code 2 before any model is loaded. See docs/claude-code.md. |
ARC_CACHE_DIR |
Root of the OpenVINO compile cache; a sub-folder per device is created below it. Unset or empty: ~/.arc-runner/cache/ov_cache. If the folder cannot be created or written, the cache is switched off with a WARNING and the model still loads. |
ARC_WARMUP_ON_LOAD |
on runs one short fixed generation (4 new tokens) right after a model loads; off or empty (default) skips it. Case-insensitive; any other value is logged at WARNING and treated as off. |
ARC_NATIVE_ALLOW_MISSING_SIDECAR |
Exactly 1 lets the native library load when its .build.json sidecar is missing (a WARNING is logged and integrity is not checked). It never excuses a sidecar that is present but has a wrong hash. |
ARC_ZE_LOADER_ALLOW_ANY |
Exactly 1 lets ARC_ZE_LOADER_PATH point at a file whose name does not look like a Level Zero loader (ze_loader, libze_loader.so*). Without it, an override must be an existing regular file with such a name, and a refused override yields no loader (the default names are not tried instead). The override is code loaded into the process; use only for a file you trust. |
This manifest has not been applied to a cluster. It assumes the Intel GPU device plugin exposes gpu.intel.com/i915. Replace <version> with a released tag.
apiVersion: apps/v1
kind: Deployment
metadata:
name: arc-runner-deployment
labels:
app: arc-runner
spec:
replicas: 1
selector:
matchLabels:
app: arc-runner
template:
metadata:
labels:
app: arc-runner
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: arc-runner
image: ghcr.io/techdeveloper-org/intel-arc-models-runner:<version>
imagePullPolicy: IfNotPresent
env:
- name: ARC_API_KEY
valueFrom:
secretKeyRef:
name: arc-runner-secrets
key: api-key
ports:
- containerPort: 8000
name: http
resources:
limits:
gpu.intel.com/i915: 1
memory: "16Gi"
cpu: "4"
requests:
gpu.intel.com/i915: 1
memory: "8Gi"
cpu: "2"
livenessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 15
periodSeconds: 30
volumeMounts:
- name: model-cache
mountPath: /home/appuser/.cache/arc-runner/models
volumes:
- name: model-cache
persistentVolumeClaim:
claimName: arc-runner-models-pvcThe probe path / (and /health) stays open when ARC_API_KEY is set; every other route then needs the key. Kubelet probes address the pod by IP, which the Host check accepts; a probe or client that uses a name needs it in ARC_ALLOWED_HOSTS. The memory and CPU figures are placeholders to adjust, not measured requirements.
| Variable | Effect |
|---|---|
ARC_API_KEY |
When set and non-empty, every route except /, /health, /docs, /openapi.json, paths below /static/ and GET/HEAD /api/hello (the Claude Code startup probe) requires the key, sent as Authorization: Bearer <key> or X-API-Key: <key>; OPTIONS preflights are never refused for a missing key. The /ui page needs the key. Missing or wrong key returns HTTP 401; /v1/messages* answer 401 in the Anthropic error envelope (authentication_error). Unset means no authentication; arc-runner serve then logs a WARNING when --host is not a loopback address. |
ARC_ALLOWED_HOSTS |
Comma-separated extra host names accepted in the Host header (names only, case-insensitive, port ignored; whitespace is trimmed; entries with a port, scheme, wildcard or slash are ignored with a warning). Always accepted: localhost, 127.0.0.1, [::1] and any other IP literal. Any other Host (or a missing, empty or repeated one) gets HTTP 421 HOST_NOT_ALLOWED, on every route including /health. This blocks DNS rebinding. Set it when clients reach the server by a name, for example ARC_ALLOWED_HOSTS=arc-runner,arc.example.lan for a Kubernetes service name or a reverse proxy that forwards the original Host. |
ARC_CORS_ORIGINS |
Comma-separated extra origins allowed by CORS and by the cross-origin check below. Local origins (localhost, 127.0.0.1, [::1], any port) are always allowed. Credentials are not allowed. |
ARC_ALLOWED_PATHS |
Extra directories (separated by the OS path separator: : on Linux, ; on Windows) that request-supplied paths may point into. Model names and paths outside the models directories and this list are refused with HTTP 403 PATH_NOT_ALLOWED. |
Browser-facing checks (always on, no setting):
- A request that carries an
Originheader must come from an allowed origin (the local origins,ARC_CORS_ORIGINS, or the server's own origin as given inHost); otherwise HTTP 403ORIGIN_NOT_ALLOWED(permission_erroron/v1/messages*). Requests withoutOrigin(curl, SDKs, Claude Code) are not affected. A state-changing request that a browser marksSec-Fetch-Site: cross-sitewithout anOriginis refused too. POST,PUT,PATCHandDELETErequests that carry a body must declareContent-Type: application/json(parameters such ascharsetand any letter case are fine); otherwise HTTP 415.curl -dwithout-H 'Content-Type: application/json'sends a form content type and is now refused./api/pullaccepts only a curated alias or a plain repository name that is a safe directory name (letters, digits,.,_,+,-, at most 128 characters, optional:tag); names with separators such asorg/model,..or absolute paths get HTTP 400INVALID_MODEL_NAMEbefore anything is created.- Error responses carry the requested model name or a stable message, not filesystem paths or raw exception text; the detail is in the server log.
In the container, ./models is mounted at /models. To let the server read models there, start it with --model-dir /models or add /models to ARC_ALLOWED_PATHS.
Container properties taken from the Dockerfile: non-root appuser (UID 1000), supplementary groups video (44) and render (107), a HEALTHCHECK on GET /, and a runtime stage without the compiler toolchain. The Intel apt repository key is downloaded during the build without a fingerprint check; that is an open item for the maintainer.
pypi-publish.ymlruns on a published GitHub release only. It checks that the tag equalsvVERSION, runscheck_docs_sync.pyand the non-hardware tests, builds the wheel and sdist, and uploads from a separate job that uses the GitHub environmentpypi(configure required reviewers on it) with PyPI trusted publishing. Branch pushes and manual runs never upload.docker-build.ymlpushes to GHCR only for a published release (section 3.3).
- No release is tagged or published;
pip install arc-runnerand the GHCR image do not work yet. - LICENSE: pending. The repository has no
LICENSEfile yet.pyproject.tomland the README name Apache-2.0, but the license text must be added by the maintainer (holder and year) before the first public upload; wheel and sdist currently contain no license text. - Maintainer setup before the first release: create the PyPI project and trusted publisher for
arc-runner(repositorytechdeveloper-org/intel-arc-models-runner, workflowpypi-publish.yml, environmentpypi), create thepypienvironment with a required reviewer, and pin the base image digest.