Skip to content
Get started

Deployment troubleshooting

Four failures eat the first deploy. Run the preflight before anything else — it names the misconfiguration instead of making you guess:

Terminal window
pnpm import:self-host-check
Terminal window
docker run --rm --env-file .env ghcr.io/capitality-io/mildport-aio \
bun dist/cli/self-host-check.js

On Kubernetes, helm test mildport is the same check inside the cluster.

Images live at ghcr.io/capitality-io/* and they are private. A pod stuck in ImagePullBackOff / ErrImagePull is almost always one of:

  1. Access not granted yet. Request it from Self-hosting in your account for the GitHub user that will pull. Pending grants look like a bad password.

  2. The node is not logged in. A laptop docker login does not help the cluster.

    Terminal window
    echo $GITHUB_TOKEN | docker login ghcr.io -u <your-github-username> --password-stdin

    On Kubernetes, those credentials belong in an imagePullSecret:

    Terminal window
    kubectl create secret docker-registry ghcr-pull \
    --docker-server=ghcr.io \
    --docker-username=<your-github-username> \
    --docker-password=$GITHUB_TOKEN \
    -n mildport
    imagePullSecrets:
    - name: ghcr-pull
  3. Wrong registry. We publish to GHCR, not Docker Hub. If values still say capitality/mildport, the pull will never succeed. See Image access.

401 means the license key did not verify. The body looks like { "status": "error", "code": "UNAUTHORIZED", "reason": "…" }. The reason is the fix:

reason What it means What to do
missing No Authorization: Bearer … header Pass the license key from Licenses
not_configured Engine has no IMPORT_LICENSE_PUBLIC_KEY Paste the public key from the Self-hosting page; restart
expired The key’s exp is in the past Regenerate on Licenses; the old key works until it expires
revoked The license id is on IMPORT_LICENSE_REVOKED_IDS Take it off, or use a new key
malformed / bad_signature / bad_attestation Truncated key, extra whitespace, or a key from a different issuer Copy the key again; do not mint one yourself

A missing public key is also a readiness failure (checks.license: missing) — the engine answers /health and still 401s every import.

Not a 401:

  • 403 ORIGIN_NOT_ALLOWED — the key is valid, but it was minted with an origin list that does not include this page. Regenerating the key with the right origins (or an empty list) is the fix. This is not CORS.
  • CORS in the browser — the request never gets a JSON body. Next section.

Confirm a key against a running engine with GET /api/import/v1/license/verify. More on keys: Licensing.

The production image (NODE_ENV=production) refuses every cross-origin browser call when ALLOWED_ORIGINS is empty — including http://localhost:5173. Dev-mode localhost is allowed only outside production.

Set the exact origin of the page that embeds the widget, scheme and port included:

Terminal window
ALLOWED_ORIGINS=https://app.example.com,http://localhost:5173

On Helm, that is app.allowedOrigins. Server-to-server callers (no Origin header) are unaffected; skip the variable only if nothing in a browser talks to the engine.

The license-key origin list (403 ORIGIN_NOT_ALLOWED) is a second, separate lock. CORS decides whether the browser may speak; the key decides whether that origin may use this key.

GET /health is liveness. It returns {"status":"ok","service":"mildport",…} as soon as the process can answer, before Mongo or licensing work. The chart’s readiness probe is GET /health/ready. A pod that lives but never becomes Ready is failing that probe.

Terminal window
curl -i localhost:8090/health/ready

503 with a body:

{
"status": "not_ready",
"service": "mildport",
"checks": { "mongo": "down", "license": "missing" }
}
Check down / missing means Fix
mongo The engine cannot ping Mongo (wrong URI, network policy, Mongo not up) MONGO_CONNECTION_STRING; wait for Mongo; helm test
license No public key and development mode off IMPORT_LICENSE_PUBLIC_KEY from the Self-hosting page

license: dev-mode is ready — synthetic keys work, and you should not ship that.

Sidecars being down does not fail readiness. Unset decode URLs just mean those file families are declined. A PDF that 503s while /health/ready is 200 is a sidecar URL, not a stuck pod.

Next: Your first import · Configuration · Image access