> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qodo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Private certificate authority

> Make Qodo on-premises trust your private certificate authority (CA) for Git providers and identity providers.

On-premisesGitHub EnterpriseGitLabBitbucket

Use this guide when a Git provider or an identity provider (IdP) shows a certificate that a private certificate authority (CA) signed. Without your CA, Qodo services cannot make HTTPS calls to that system.

Qodo trusts your CA in addition to the public CAs already in the images. You give Qodo only your private CA certificates. You do not give it public root certificates.

> [!NOTE]
>
> This guide applies to the Qodo on-premises Helm chart that includes the `custom-ca-cert` Secret. If your values file still sets `global.caBundle`, read [Migrate from global.caBundle](#migrate-from-global-cabundle) first.

## How it works

You create one optional Kubernetes Secret named `custom-ca-cert`. Each key in the Secret holds one CA certificate file.

When a pod starts, an init container named `ca-builder` does these steps:

1. It copies the trust store of the image (`/etc/ssl/certs/ca-certificates.crt`) to a temporary volume.
2. It then adds every certificate from the Secret to that copy.
3. The application container mounts the merged file in place of the original.

The chart sets the `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE`, and `GIT_SSL_CAINFO` variables to the merged file. Python and Git then use it.

If the Secret does not exist, the pod starts and uses the trust store of the image without changes.

### What the chart covers

The `ca-builder` init container runs in these deployments:

* `qodo-engine-indexer`
* `qodo-engine-pr-kn-consumer`
* `qodo-engine-retriever`
* `qodo-git`
* `qodo-git-event-consumer`
* `qodo-gitway`
* `qodo-litegit`
* `qodo-platform`
* `qodo-pr-agent-agentic`
* `qodo-agent-runtime-qar`
* `qodo-user-to-allowed-repos-consumer`
* `qodo-zitadel`

It also runs in the `qodo-sync-installations` CronJob.

The chart does not cover these workloads:

* The analytics and health-probe sidecar containers in `qodo-git`.
* The bundled LLM gateway (LiteLLM).

> [!WARNING]
>
> The bundled LLM gateway does not trust your private CA. If your LLM provider endpoint uses a private-CA certificate and you call it through the bundled gateway, the call fails the certificate check. Use an endpoint with a publicly trusted certificate for that provider.

## Prerequisites

Complete these steps before installing or upgrading the Helm chart.

### Prepare the certificate files

Each file must be in PEM format. A PEM file is text. It starts with `-----BEGIN CERTIFICATE-----`.

> [!WARNING]
>
> Do not use DER (binary) files. If one DER file is in the Secret, the whole merged bundle breaks silently, and every HTTPS call can fail.

**1. Check the PEM header**

Check that the first line of each file is the PEM header.

```bash theme={null}
head -n 1 /path/to/my-corp-ca.pem
```

The output must be `-----BEGIN CERTIFICATE-----`.

**2. Check that the file is a valid certificate**

```bash theme={null}
openssl x509 -in /path/to/my-corp-ca.pem -noout -subject -enddate
```

If this command fails with `Could not read certificate`, the file is not PEM. Continue to the next step.

**3. Convert DER to PEM**

If the file is DER, convert it to PEM.

```bash theme={null}
openssl x509 -inform der -in ca.crt -out ca.pem
```

A `.crt` or `.cer` file can be PEM or DER. Always do step 1 or step 2 to check.

### Create the Secret

**1. Create the Secret**

Create the Secret before you install or upgrade the chart. Use the same namespace as the release.

```bash theme={null}
kubectl create secret generic custom-ca-cert -n <namespace> \
  --from-file=my-corp-ca.pem=/path/to/my-corp-ca.pem
```

**2. Add chain certificates (if applicable)**

If your CA has a chain, add one `--from-file` option for each certificate.

```bash theme={null}
kubectl create secret generic custom-ca-cert -n <namespace> \
  --from-file=root-ca.pem=/path/to/root-ca.pem \
  --from-file=intermediate-ca.pem=/path/to/intermediate-ca.pem
```

**Step 3**

Supply only your private CA certificates. Qodo already trusts the public roots.

## Install or upgrade

Run your normal `helm upgrade --install` command. You do not set any value for this feature. The chart finds the Secret by its name.

## Verify the setup

**1. Read the init container log**

Read the init container log of a pod.

```bash theme={null}
kubectl logs -n <namespace> deploy/qodo-platform -c ca-builder
```

The log shows one line for each certificate:

```text theme={null}
Adding custom cert: /custom-ca/my-corp-ca.pem
```

If the Secret is not present, the log shows `No custom certs found, using system bundle only`.

**2. Check that your CA is in the merged file**

The command uses the second line of your PEM file as a search key.

```bash theme={null}
LINE=$(sed -n 2p /path/to/my-corp-ca.pem)
kubectl exec -n <namespace> deploy/qodo-platform -- \
  grep -c -F "$LINE" /etc/ssl/certs/ca-certificates.crt
```

The output must be `1` or more.

**3. Make an HTTPS call to your private endpoint**

The `qodo-platform` image includes `python3`.

```bash theme={null}
kubectl exec -n <namespace> deploy/qodo-platform -- \
  python3 -c "import urllib.request; print(urllib.request.urlopen('https://git.example.com').status)"
```

Replace `https://git.example.com` with the address of your Git provider or IdP. A successful call prints an HTTP status code such as `200`. A certificate error means the pod does not trust your CA.

## Add or rotate certificates

Kubernetes does not restart pods when a Secret changes. The chart never reads the contents of the Secret. Do these steps after you add, replace, or remove a certificate.

**1. Update the Secret**

This command replaces the Secret with the new files.

```bash theme={null}
kubectl create secret generic custom-ca-cert -n <namespace> \
  --from-file=my-corp-ca.pem=/path/to/my-corp-ca.pem \
  --dry-run=client -o yaml | kubectl apply -f -
```

**2. Restart the Deployments**

```bash theme={null}
kubectl rollout restart deployment -n <namespace>
```

New `qodo-sync-installations` Jobs read the Secret when they start. You do not restart them.

### Handle a running repository sync

A sync Job that is already running keeps the certificates it had at startup. Wait for it to finish. This is the safe choice.

**1. List the sync Jobs**

```bash theme={null}
kubectl get jobs -n <namespace> | grep qodo-sync-installations
```

**2. Wait for the active Job to complete**

```bash theme={null}
kubectl wait -n <namespace> --for=condition=complete \
  job/<active-sync-job-name> --timeout=4h
```

> [!WARNING]
>
> Do not delete a running sync Job unless you must. Installations that the Job claimed stay in the `IN_PROGRESS` state. The platform releases them only after a stale-claim reset of about 4 hours. Until then, those installations do not sync.

When you add a CA, the running Job does not need a restart. When you retire a CA, a running Job still trusts it until it ends. Wait for the Job to finish if you can.

## Migrate from global.caBundle

Earlier chart versions used `global.caBundle`. It held a full PEM bundle in your values file and replaced the trust store. The chart no longer reads this key.

If your values file still sets `global.caBundle`, Helm stops with an error that tells you to migrate. This is by design. It prevents a silent loss of trust.

**Step 1**

Copy your private CA certificate into a PEM file. Your old bundle held public roots and your CA. Take only your CA.

**2. Create the Secret**

```bash theme={null}
kubectl create secret generic custom-ca-cert -n <namespace> \
  --from-file=my-corp-ca.pem=/path/to/my-corp-ca.pem
```

**Step 3**

Delete `global.caBundle` from your values file.

**Step 4**

Run `helm upgrade --install` again.

If you set `global.ingress.caBundle`, delete it too. The chart ignores this key.

## GitOps and external secrets

The chart does not create the `custom-ca-cert` Secret. Create it outside the chart.

* Argo CD or Flux: Apply the Secret from your own source of truth, or create it with `kubectl`. Do not put the certificates in the chart values.
* External Secrets Operator: Make the `ExternalSecret` create a Secret named `custom-ca-cert` in the release namespace. Each key must hold one PEM certificate.

A GitOps tool does not restart pods when this Secret changes. After a change, run `kubectl rollout restart deployment -n <namespace>`.

## Configure pod security settings for OpenShift

No OpenShift-specific setting is required for this feature. Set `global.podSecurityContext` as your cluster requires. The `ca-builder` init container uses the same pod settings as the application container. For these settings, see the [Helm chart overview](https://docs.qodo.ai/on-prem/qodo-on-prem-helm-chart-overview.md).

## Troubleshooting

> [accordion] A pod is stuck in the Init state
>
> **1. Read the init container log**
>
> ```bash theme={null}
> kubectl logs -n <namespace> <pod-name> -c ca-builder
> ```
>
> **2. Read the pod events**
>
> ```bash theme={null}
> kubectl describe pod -n <namespace> <pod-name>
> ```
>
> A `Permission denied` error on the copy step means the pod cannot write to the temporary volume. Check that `global.podSecurityContext` includes an `fsGroup` that your cluster allows.

> [accordion] Calls to the private endpoint still fail
>
> Check these causes in order:
>
> 1. The file is DER and not PEM. Do the checks in [Prepare the certificate files](#prepare-the-certificate-files).
> 2. The Secret is in the wrong namespace. The Secret and the release must be in the same namespace.
> 3. The pods did not restart. Run `kubectl rollout restart deployment -n <namespace>`.
> 4. The Secret does not hold the full chain. Add the intermediate certificates as extra keys.
> 5. The call comes from a workload that the chart does not cover, such as the `qodo-git` sidecar containers or the bundled LLM gateway.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.