---
title: "Self-hosted processor"
canonical_url: "https://docs.getdx.com/ai-code-insights/self-hosted-processor/"
md_url: "https://docs.getdx.com/ai-code-insights/self-hosted-processor.md"
last_updated: "2026-10-14"
---

# Self-hosted processor
> The self-hosted processor is currently in beta. Contact your DX account team before you deploy it.


The self-hosted processor keeps AI Code Insights session transcripts inside your network. Developer laptops send sessions to a processor that you run instead of to DX. The processor stores transcripts on its own volume, runs session summaries, work type assignment, and evaluations with your Amazon Bedrock account, and sends DX only session metadata and analysis results, never titles or message content.

Only session uploads change destination. Commits, settings, updates, and all other daemon traffic continue to go to DX. DX never connects to the processor; the processor calls DX.

> **Note**: The processor requires AI Code Insights daemon 0.7.8 or later. DX turns on processor mode for your organization.

## Before you begin

- **Daemon 0.7.8 or later** on every developer laptop, or a plan to get there with [automatic updates](https://docs.getdx.com/ai-code-insights/mdm-installation/#automatic-updates).
- **A Linux container platform**, such as Kubernetes or Amazon ECS, that can run one container with a persistent local volume.
- **An internal hostname and TLS certificate** for the processor. A certificate from your organization's own certificate authority works when developer laptops trust that authority.
- **Network access from every developer laptop** to that hostname, through your corporate network, VPN, or zero-trust access proxy.
- **Amazon Bedrock access** in an AWS account you control. See [Step 2: Deploy the processor](#step-2-deploy-the-processor) for the required models and permission.
- **A DX [web API token](https://app.getdx.com/admin/webapi)** for the processor's connection to DX. This is not the AI Code Insights organization token.
- **Endpoint management** that can deliver one more setting to laptops: MDM on macOS and Windows, and configuration management on Linux.

## Step 1: Upgrade daemons

Open **Admin** -> **AI Code Insights** -> **Client versions** and confirm that active developers run 0.7.8 or later. Turn on [automatic updates](https://docs.getdx.com/ai-code-insights/mdm-installation/#automatic-updates) to move the remaining endpoints forward.

Daemons older than 0.7.8 do not route sessions to the processor. After processor mode is on, they keep uploading sessions to DX, and DX discards their titles and message content. Those sessions are not summarized or evaluated. Upgrade them before [Step 5: Turn on processor mode](#step-5-turn-on-processor-mode).

## Step 2: Deploy the processor

The processor is published as a public image for `linux/amd64` and `linux/arm64`:

```text
ghcr.io/get-dx/daemon-processor:latest
```

`latest` follows the newest release. To pick up new releases when the processor restarts, have your platform pull the image on every start: `imagePullPolicy: Always` on Kubernetes, or `docker run --pull always` with Docker. Otherwise a node can keep running a cached older `latest`. To choose when to upgrade instead, pin a version tag such as `v0.2.0` from the [package page](https://github.com/orgs/get-dx/packages/container/package/daemon-processor). Your DX account team tells you before a release that needs changes on your side.

The container runs as UID and GID `65532` and serves plain HTTP on port `8080`. `GET /healthz` returns `ok` while the process is up. It does not report DX connectivity or analysis progress. The processor shuts down within 10 seconds of `SIGTERM`; allow a longer grace period.

### Environment variables

| Variable                                     | Required | Description                                                                                                      |
| -------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `DATACLOUD_URL`                              | Yes      | Your DX instance URL, for example `https://yourinstance.getdx.net`.                                              |
| `DATACLOUD_KEY`                              | Yes      | The DX web API token. Pass it from a secret store.                                                               |
| `AWS_REGION`                                 | Yes      | The Bedrock region, for example `us-east-1`.                                                                     |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | No       | Only when the processor has no IAM role, for example outside AWS.                                                |
| `RETENTION_DAYS`                             | No       | Maximum age of stored content, from 1 to 365 days, measured from when the content was produced. Defaults to `7`. |
| `LISTEN_ADDR`                                | No       | HTTP listen address. Defaults to `:8080`.                                                                        |

The processor exits at startup, naming the missing variables, unless all required variables are set.

On Amazon ECS, EKS, or EC2, give the processor an IAM role (an ECS task role, IAM roles for service accounts or Pod Identity, or an instance profile) and leave the AWS keys unset. The processor calls `openai.gpt-5.6-luna` and `anthropic.claude-haiku-4-5-20251001-v1:0` through cross-Region inference profiles: the geographic profile for your Region, such as `us.`, or the `global.` profile where none exists. In EU, Japan, and Australia Regions, summaries use Claude Haiku so that transcripts stay in the geography. Enable access to both models in your account.

Granting access to the inference profiles alone is not enough. Amazon Bedrock also authorizes the underlying model in every Region a profile can route to, the Region-less model for `global.` profiles, and, for GPT-5.6 Luna, your account's default Bedrock project. This policy covers all of them for any Region. Replace `ACCOUNT_ID` with your AWS account ID:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "InferenceProfiles",
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": [
        "arn:aws:bedrock:*:ACCOUNT_ID:inference-profile/*.openai.gpt-5.6-luna",
        "arn:aws:bedrock:*:ACCOUNT_ID:inference-profile/*.anthropic.claude-haiku-4-5-20251001-v1:0"
      ]
    },
    {
      "Sid": "ModelsThroughInferenceProfiles",
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": [
        "arn:aws:bedrock:*::foundation-model/openai.gpt-5.6-luna",
        "arn:aws:bedrock:*::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0",
        "arn:aws:bedrock:::foundation-model/openai.gpt-5.6-luna",
        "arn:aws:bedrock:::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0"
      ],
      "Condition": {
        "StringLike": {
          "bedrock:InferenceProfileArn": "arn:aws:bedrock:*:ACCOUNT_ID:inference-profile/*"
        }
      }
    },
    {
      "Sid": "LunaDefaultProject",
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:*:ACCOUNT_ID:project/default"
    }
  ]
}
```

If service control policies in your organization block Regions, they must allow the destination Regions of your Region's profiles. See [geographic](https://docs.aws.amazon.com/bedrock/latest/userguide/geographic-cross-region-inference.html) and [global](https://docs.aws.amazon.com/bedrock/latest/userguide/global-cross-region-inference.html) cross-Region inference for the requirements.

The processor does not check credentials at startup. Missing credentials, denied access, or an unavailable model pause analysis and log `processor analysis paused` with the AWS error code until you fix them. These errors never cause the processor to give up on a session, but retention still deletes content older than `RETENTION_DAYS`, so a misconfigured role loses those sessions' analyses. After deploying, end a test session on a routed laptop and confirm that the processor logs `processor analysis reported` within a few minutes.

### Storage and replicas


> **Never run more than one processor for your organization.** Each processor holds only the transcripts uploaded to it, but DX hands analysis work to whichever processor asks. A second processor, whether an extra replica, a rolling or blue-green deployment, or a test instance pointed at the same DX organization, is handed sessions it does not hold and gives up on them. Those sessions never get summaries, work types, or evaluations, and nothing reports the loss. To replace the processor, stop the old one before starting the new one on the same volume.


On Kubernetes, use a StatefulSet, or a Deployment with `replicas: 1` and the `Recreate` strategy, never a rolling update.

Give the processor a persistent volume. It holds the only copy of each transcript until the transcript is analyzed, because laptops delete a session from their local queue once the processor stores it. Without a persistent volume, every restart loses the sessions waiting for analysis.

The processor stores sessions in SQLite, which needs a local or block-storage volume, not a network file system such as NFS or EFS. With Docker, mount a named volume at `/data`. On Kubernetes, follow the example below, which handles the volume's permissions.

The processor deletes content older than `RETENTION_DAYS` at startup and hourly. Deletion removes database rows; it does not securely erase disk pages or volume snapshots.

### Example Kubernetes deployment

Adapt this example to your cluster. It assumes the ingress-nginx controller with an ingress class that is reachable only from your network, an existing TLS secret, and a service account bound to an IAM role with Bedrock access.

```bash
kubectl create secret generic daemon-processor \
  --from-literal=datacloud-key="$DX_WEB_API_TOKEN"
```

```yaml
apiVersion: v1
kind: Service
metadata:
  name: daemon-processor
spec:
  selector:
    app: daemon-processor
  ports:
    - name: http
      port: 80
      targetPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: daemon-processor
spec:
  serviceName: daemon-processor
  replicas: 1 # Never run more than one.
  selector:
    matchLabels:
      app: daemon-processor
  template:
    metadata:
      labels:
        app: daemon-processor
    spec:
      serviceAccountName: daemon-processor # Bound to an IAM role with Bedrock access.
      terminationGracePeriodSeconds: 30
      securityContext:
        runAsUser: 65532
        runAsGroup: 65532
        fsGroup: 65532 # Lets the processor write to the new volume.
      containers:
        - name: daemon-processor
          image: ghcr.io/get-dx/daemon-processor:latest
          imagePullPolicy: Always # Kubernetes keeps an earlier default when only the tag changes.
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: DATA_DIR # The volume's root is not private, so store in a subdirectory.
              value: /var/lib/daemon-processor/data
            - name: DATACLOUD_URL
              value: https://yourinstance.getdx.net
            - name: DATACLOUD_KEY
              valueFrom:
                secretKeyRef:
                  name: daemon-processor
                  key: datacloud-key
            - name: AWS_REGION
              value: us-east-1
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
          volumeMounts:
            - name: data
              mountPath: /var/lib/daemon-processor
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 20Gi # Size for RETENTION_DAYS of your organization's sessions.
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: daemon-processor
  annotations:
    # Laptops send request bodies of up to 2 MiB; ingress-nginx allows 1 MiB by default.
    nginx.ingress.kubernetes.io/proxy-body-size: "32m"
spec:
  ingressClassName: internal-nginx # Reachable only from your network.
  tls:
    - hosts:
        - ai-processor.internal.example.com
      secretName: daemon-processor-tls
  rules:
    - host: ai-processor.internal.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: daemon-processor
                port:
                  name: http
```

## Step 3: Expose the processor to developer laptops

Every developer laptop connects to the processor, so the processor needs inbound access from your developer network, unlike the daemon's outbound-only connections to DX. Before you configure laptops, confirm each item:

- **Internal only.** The upload endpoint does not authenticate uploaders. Anyone who can reach it can submit sessions under any developer identity. Never expose it to the internet.
- **HTTPS at the ingress or load balancer**, forwarding plain HTTP to port `8080`. The daemon uses the operating system's trusted certificates, so a certificate from your organization's certificate authority works on laptops that trust it.
- **The processor at the root of its hostname.** `processor_url` is an origin, such as `https://ai-processor.internal.example.com`, and cannot include a path, so the processor cannot share a hostname under a path prefix.
- **Request bodies of at least 2 MiB** through every proxy and load balancer. A proxy that rejects a body with HTTP `413` causes the laptop to discard that part of the session.
- **Reachable on every network path** developers use: office, VPN, and remote access.
- **Outbound access from the processor** to your DX instance URL and to Amazon Bedrock in `AWS_REGION`.

From a developer laptop, confirm that DNS, TLS, and routing work:

```bash
curl https://ai-processor.internal.example.com/healthz
```

The command prints `ok`. Repeat it from each network path.

Laptops that cannot reach the processor keep sessions in a local queue and retry. They never fall back to uploading transcripts to DX. The processor does not store content that is already older than `RETENTION_DAYS` when it arrives, so a laptop that stays off the network for longer than that loses analysis for its older sessions.

## Step 4: Configure laptops

Deliver `processor_url` through the same channel as your other AI Code Insights settings. Managed values on macOS and Windows take priority over the config file unless the developer sets [`prefer_local_config`](https://docs.getdx.com/ai-code-insights/configuration/#prefer_local_config).



### macOS

Add the key to the `com.getdx.aicodemetrics` payload of your Configuration Profile:

```xml
<key>processor_url</key>
<string>https://ai-processor.internal.example.com</string>
```

### Windows

Deliver `processor_url` as `REG_SZ` under `HKLM\SOFTWARE\GetDX\AICodeMetrics`.

### Linux

Add `processor_url` to `~/.aicodemetrics/config.json`:

```json
{
  "api_url": "API_URL",
  "api_key": "API_TOKEN",
  "email": "developer@example.com",
  "processor_url": "https://ai-processor.internal.example.com"
}
```



The value must be an HTTPS origin with no path, query, or fragment. Daemon 0.7.7 and later stop monitoring while `processor_url` is invalid and log `Waiting for valid URL configuration`. Deliver it to a few laptops and check them before rolling it out widely.

Delivering `processor_url` is safe before processor mode is on. Until then, daemons 0.7.8 and later keep uploading sessions to DX, and older daemons ignore the setting.

The daemon reads `processor_url` at startup. Restart the daemon or wait for its next restart, such as an automatic update or a reboot. Then run [`aicodemetricsd status`](https://docs.getdx.com/ai-code-insights/configuration/#status). Under **Session processor**, **Configured URL** shows your processor URL followed by **configured but inactive**.

## Step 5: Turn on processor mode

When laptops report the processor URL, ask DX to turn on processor mode for your organization. Processor mode applies to every laptop in the organization.

Daemons pick up the change when they refresh DX settings, at startup and hourly, without a restart. **Configured URL** then shows **active**, and new sessions appear in DX with summaries and evaluations from the processor within about an hour of going idle.

After processor mode is on, each laptop's sessions follow its daemon version and configuration:

| Daemon           | `processor_url` | Session uploads                                                                                                                 |
| ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| 0.7.8 or later   | Set             | Sent to the processor.                                                                                                          |
| 0.7.8 or later   | Not set         | Held in a local queue of up to 256 MiB until `processor_url` is configured and the daemon restarts. `status` reports a failure. |
| Older than 0.7.8 | Either          | Uploaded to DX, which discards titles and message content. These sessions are not summarized or evaluated.                      |

Laptops routed to the processor send it scrubbed transcripts and titles regardless of the **Transcripts & Agent Experience** setting in DX. That setting continues to control transcripts uploaded to DX.

If DX turns processor mode off, laptops return to uploading sessions to DX within an hour. Sessions already queued for the processor are still delivered to it.

## Next steps

- [Enterprise deployment](https://docs.getdx.com/ai-code-insights/mdm-installation/) describes the managed settings channels and installers used to deliver `processor_url`.
- [Security and privacy](https://docs.getdx.com/ai-code-insights/security-and-privacy/) explains what the daemon collects and how transcripts are scrubbed.
- [Troubleshooting](https://docs.getdx.com/ai-code-insights/troubleshooting/) covers missing or stale data after rollout.
---

## Sitemap

[Overview of all docs pages](/llms.txt)
