Some teams cannot run CARL's hosted assessor. Regulated industries, classified systems, internal forks with licensing constraints, air-gapped networks — all have legitimate reasons to keep their source entirely inside their own perimeter. The self-hosted CARL Docker image solves this.
This guide covers what the self-hosted image does, how to run it, and what
differs from the hosted assessor at carl.wentzel.ai/app.
What the image contains
A single container image with:
- The deterministic engine (
@wentzel/carl-engine) — full v1.0 criteria catalog, every language and framework detector. - A prompt runner using an authenticated, app-scoped Cloudflare AI Gateway connection.
- The worker logic — job lifecycle, sanitization, report writing, but running as a local daemon instead of the hosted Cloudflare Worker.
- An embedded web UI similar to
carl.wentzel.ai/appfor running assessments, viewing reports, and exporting PDFs. - An optional report store — SQLite by default, or point at your own database instance.
No external network traffic is required after the image is pulled. If you want AI-assisted evaluation, configure an authenticated Cloudflare AI Gateway slice; the product has no direct-provider, Bedrock, local-model, or OpenAI-compatible bypass.
Minimum system requirements
- Container runtime — Docker 24+, Podman 4+, or a Kubernetes cluster with a functioning CRI.
- Memory — 2 GB for single-concurrency use; 4 GB recommended.
- CPU — 2 vCPU minimum.
- Storage — 2 GB for the image; additional space for report storage proportional to how many reports you retain.
- Postgres — bring your own or use the bundled sidecar (not production-grade but fine for small teams).
Quickstart (when it ships)
# Pull the image
docker pull ghcr.io/wentzel-ai/carl:latest
# Run with the bundled Postgres, no AI-assisted evaluation
docker run -d --name carl \
-p 8080:8080 \
-v carl-data:/var/carl/data \
ghcr.io/wentzel-ai/carl:latest
# Open the UI
open http://localhost:8080
Or with a docker-compose.yml:
services:
carl:
image: ghcr.io/wentzel-ai/carl:latest
restart: unless-stopped
ports:
- '8080:8080'
environment:
DATABASE_URL: 'postgres://carl:carl@db:5432/carl'
# Optional — configure if you want AI-assisted evaluation.
ANTHROPIC_API_KEY: '${ANTHROPIC_API_KEY:-}'
ANTHROPIC_API_URL: '${ANTHROPIC_API_URL:-}'
depends_on: [db]
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: carl
POSTGRES_PASSWORD: carl
POSTGRES_DB: carl
volumes:
- carl-db:/var/lib/postgresql/data
volumes:
carl-data: {}
carl-db: {}
Running assessments
-
Clone the repository you want to assess onto the machine running the container (or mount the repository as a volume, if the repo is large).
docker run --rm \ -v /path/to/my-repo:/workspace:ro \ ghcr.io/wentzel-ai/carl:latest assess /workspace --json -
Or use the web UI by browsing to
http://localhost:8080, choosing "Assess a local path," and entering the path (which must be mounted into the container). -
View or download the report from the UI, or consume the JSON from the command above.
Capability differences from the hosted assessor
The self-hosted image aims to match the hosted assessor's assessment semantics exactly — the engine, the criteria catalog, the prompts, and the output schema are all identical. Operational differences are:
| Capability | Hosted | Self-hosted |
|---|---|---|
| Repository assessment | ✅ (public repos; single-operator today) | ✅ |
| Private-repo OAuth flow | Planned (M5) | Manual clone; OAuth flow not included |
| AI-assisted evaluation | Planned (deterministic-only today) | BYOK only; configure your own endpoint |
| Progress streaming | Planned (M5, SSE) | ✅ |
| PDF export | Print-to-PDF today; export endpoint later | ✅ |
| Organization dashboard | M9 | Included |
| Badge service | M8 | Ships the SVG endpoint locally |
| Audit log | ✅ (managed) | ✅ (your Postgres) |
| Software updates | Continuous | You pull new images |
The one capability not in the self-hosted image is the GitHub App installation flow — it requires callback URLs and webhook endpoints reachable from GitHub's infrastructure, which violates the air-gapped constraint. Use the CLI or the embedded UI instead.
Hardening checklist for production self-host
-
Put the UI behind SSO. The embedded UI supports reverse-proxy authentication (header-based) out of the box. Point Nginx or your preferred proxy at it and require your corporate auth before the request reaches CARL.
-
Rotate the Postgres credentials away from the
carl:carldefaults in the compose example. Use a secrets manager. -
Restrict container capabilities. The default image runs as non-root, drops all Linux capabilities, and runs the worker inside gVisor on compatible runtimes. Verify with
docker inspect. -
Set up log shipping to your SIEM. The container logs structured JSON to stdout — ingest with Fluent Bit, Vector, or equivalent.
-
Back up the Postgres volume. Reports are valuable data; a weekly snapshot is usually sufficient.
Upgrade policy
- Minor image updates — safe to pull and restart; report schema is stable.
- Major image updates — read the release notes. A major bump may carry a standard version bump, which requires re-assessment of existing subjects against the new version. See conformance claims.
Support
Self-hosted CARL is supported by Wentzel.ai under the terms of its License (proprietary, all rights reserved — a self-hosting license is granted separately to customers who deploy the image, distinct from the reference source itself). Commercial support and priority patching ship in milestone 11 as part of the Enterprise tier.
Related
- Private-repo assessment — the hosted assessor's architecture, which the self-hosted image replaces for stricter environments.
- CLI — for teams that don't need the full embedded UI.
- Governance — who maintains the project and how to report issues.