TheoWorks

Kubernetes

Run TheoWorks on your own Kubernetes cluster with Helm. Tested on k3s v1.34 with Traefik. One Helm chart, two editions -- Free/Pro (the public chart) and Enterprise (the offline bundle or the registry credential, both from the download page).

Note

On Red Hat OpenShift? Use the OpenShift guide -- it uses an OpenShift Route and OpenShift's built-in security policy instead of an Ingress. Red Hat OpenShift cluster? Use the OpenShift guide. Any other Kubernetes cluster? Use the Kubernetes guide.

Choose your path

| Your situation | What you'll use | |---|---| | Free or Pro | The public chart | | Enterprise, connected cluster | The registry credential | | Enterprise, air-gapped cluster | The offline bundle |

Prerequisites

  • Kubernetes 1.24 or later (tested on 1.34) -- kubectl and Helm 3.8+.
  • An Ingress controller and IngressClass.
  • A default StorageClass.
  • A namespace that allows Pod Security restricted.
  • A TLS certificate plan -- see TLS below.
  • Your trusted proxy range -- see Find your forwardedAllowIps.
  • Reachability to your GitLab, plus an OAuth application registered on it (client ID, client secret, and redirect URI). Sign-in is always through your own GitLab.
  • Free or Pro only: outbound HTTPS to lm.theoworks.io. The public chart connects to the TheoWorks License Server by default, the same way the single-machine launcher does -- your plan activates the same way. Enterprise clusters use the offline license instead and need no such access.
  • Enterprise only: sign in at theoworks.io/download to get the chart and image, your offline Enterprise license file, and jq on the air-gapped workstation (to read the image digest).

An Ingress controller

bash
kubectl get ingressclass

Use its NAME as ingress.className. If exactly one class is marked default, className may be left empty. The chart creates a standard networking.k8s.io/v1 Ingress. Gateway-API-only clusters aren't covered yet.

Storage

bash
kubectl get storageclass

One entry should show (default). If none does, set both persistence.*.storageClassName. The pod needs two ReadWriteOnce volumes (index 2Gi, state 256Mi). Its security context uses fsGroup to make them writable, so the storage driver must apply fsGroup. Tested with k3s's local-path storage.

Pod Security

The pod is proven admitted under the restricted Pod Security profile. The guide recommends labelling the namespace pod-security.kubernetes.io/enforce=restricted (the Install steps below do this). A looser profile (baseline, privileged, or no label) installs unchanged -- keep the security context as written, don't remove it. Pod Security has no level above restricted; if your cluster adds a policy engine (for example Kyverno or Gatekeeper), give the platform team this table:

| What the pod asks for | Value | |---|---| | Runs as | non-root, UID 10001, GID 0, fsGroup 0 | | Root filesystem | read-only | | Privilege escalation | not allowed | | Capabilities | all dropped | | Seccomp profile | RuntimeDefault | | Volumes | two RWO volume claims, plus an in-memory /tmp | | Host access | no host paths, no host network |

Other user or group IDs aren't tested yet.

Find your forwardedAllowIps

The app only trusts forwarded client addresses from this range, and the chart refuses to install without it.

  1. Find the controller's pods and their IPs, filtered to the controller's namespace as shown by kubectl get ingressclass:

    bash
    kubectl get pods -A -o wide
  2. Find the pod network that contains them:

    bash
    kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'

    The per-node ranges sit inside the cluster pod network -- for example, on k3s 10.42.0.0/24 and 10.42.1.0/24 sit inside 10.42.0.0/16.

If podCIDR is empty (as with some network plugins), ask your platform team for the cluster's pod network range. If the controller runs on the host network (its pod IP equals a node IP), use the node network range instead. A comma-separated list is allowed; * is never accepted -- TheoWorks then trusts no forwarded addresses at all. Tested default: k3s 10.42.0.0/16 -- confirm it on your cluster.

Get the chart and image

Free or Pro

The public chart pulls the public TheoWorks image anonymously — no registry credential, no image pull secret, no sign-in. Its OCI reference (public ECR) is on the download page — the site has the current version; this page's commands install without pinning a version so they always install the current release.

Enterprise, air-gapped cluster

At theoworks.io/download, the Air-gapped cluster card gives you Get the offline bundle: the chart archive, the image as a portable archive, checksums, and the signing key:

text
theoworks-enterprise-X.Y.Z.tgz
theoworks-app-enterprise-X.Y.Z.oci.tar
checksums.txt
cosign.pub

Download all four before the link window closes — the window only has to cover the start of each download; a download already in progress finishes.

Enterprise, connected cluster

At theoworks.io/download, the Connected cluster card gives you Get a registry credential: a registry host, a username, and a temporary password, shown once and copyable without revealing it.

Write your values.yaml

The complete file below is byte-identical to the Kubernetes template on the download page -- if you started there, this is the same file you already copied.

yaml
route:
  enabled: false                        # plain Kubernetes uses an Ingress, not an OpenShift Route
ingress:
  enabled: true
  className: traefik                    # your IngressClass -- kubectl get ingressclass
  host: theoworks.example.com           # your external hostname
  tlsStrategy: own-cert                 # own-cert | acme | none -- see the guide's TLS section
  tlsSecretName: theoworks-tls          # the TLS Secret holding your hostname's certificate
forwardedAllowIps: "10.42.0.0/16"       # your ingress controller's pod network -- required

podSecurityContext:                     # Kubernetes doesn't assign these the way OpenShift does
  runAsUser: 10001
  runAsGroup: 0
  fsGroup: 0

config:
  gitlabInstance: "https://gitlab.example.com"                          # your GitLab URL
  oauthRedirectUri: "https://theoworks.example.com/oauth/callback"   # must match the GitLab OAuth app
  maintainerEmail: "you@example.com"        # optional -- their first sign-in becomes the maintainer, skipping first-run setup

secrets:
  oauthClientId: "<your-gitlab-oauth-client-id>"
  oauthClientSecret: "<your-gitlab-oauth-client-secret>"
  sessionKey: "<32 random bytes, base64 -- e.g. `openssl rand -base64 32`>"

Kubernetes settings

  • route.enabled: false -- plain Kubernetes has no Route object; the chart's route.* keys are OpenShift-only.
  • ingress.* -- enabled: true turns on the chart's networking.k8s.io/v1 Ingress. className names your IngressClass (see An Ingress controller). host is the external hostname users reach.
  • forwardedAllowIps (top-level) -- the platform-neutral alias for the trusted proxy range (see Find your forwardedAllowIps). route.forwardedAllowIps wins over this key if both are set -- a Route-less install should leave the Route-named key unset.
  • podSecurityContext -- pins the pod's UID/GID/fsGroup explicitly, since Kubernetes (unlike OpenShift's restricted-v2) does not assign these automatically.

TLS

| Your situation | Set in ingress | Also do | |---|---|---| | You have a certificate for the hostname (e.g. from your corporate CA) -- recommended | tlsStrategy: own-cert, tlsSecretName: theoworks-tls | create the Secret during Install (its step is shown there) | | cert-manager with a ClusterIssuer (the usual Let's Encrypt setup) | tlsStrategy: own-cert, tlsSecretName: theoworks-tls, annotations: { cert-manager.io/cluster-issuer: letsencrypt } | nothing -- cert-manager creates the Secret | | cert-manager with an Issuer in this namespace | tlsStrategy: acme, certManagerIssuer: <issuer name> in values | ingress.host must be set | | TLS ends before the cluster (a load balancer terminates it) | tlsStrategy: none | use https:// in oauthRedirectUri only if users reach the site over HTTPS |

The ClusterIssuer row above uses own-cert plus an annotation, not acme, because the acme option currently supports only an Issuer in the same namespace. This flips to an acme row once the chart accepts a ClusterIssuer.

Note

tlsStrategy accepts exactly own-cert, acme or none. OpenShift's internal isn't available on Kubernetes. A misspelt value is currently not rejected: the Ingress then serves your controller's default certificate.

The acme row's "must be set" is a hard requirement today with no chart guard. The guard text changes once the chart enforces it.

GitLab connection details are never optional: config.gitlabInstance and config.oauthRedirectUri must match the OAuth application exactly, and secrets.oauthClientId/oauthClientSecret/sessionKey must all be set (or provided via secrets.existingSecret) — the app refuses to boot without them.

Storage and resources apply to every path too:

yaml
persistence:
  index:
    size: 2Gi                  # rebuildable search index
    storageClassName: ""       # "" = cluster default
  state:
    size: 256Mi                # small usage state
    storageClassName: ""

resources:
  limits:   { cpu: "2", memory: 2Gi }
  requests: { cpu: "500m", memory: 1Gi }

The pod sizes its internal work pools to the CPU limit you set here, so it behaves the same on a small node and a large one.

Enterprise additions

On top of the common values above, Enterprise sets the license -- and, only for an air-gapped cluster, the image location:

yaml
license:
  enabled: true                       # the chart default
  existingSecret: "theoworks-license" # recommended: a secret you manage
  existingSecretKey: "license.json"
  # or, for a first install, inline the envelope instead:
  # content: '{"...": "..."}'

The license is mounted as a file, so rotating it is a secret update — no pod restart.

Connected cluster: keep the chart's image settings. The Enterprise chart already names the released image and pins it by digest — never a tag. Add only the pull secret you create during Install:

yaml
image:
  pullSecrets: ["theoworks-pull"]     # your dockerconfigjson secret(s)

Don't set image.registry, image.repository, image.digest or image.tag for a connected cluster.

Air-gapped cluster: nothing to add here for the image. Install sets the image's registry and digest from your registry and the bundle. If your registry needs credentials to pull, add your own secret under image.pullSecrets.

Pin the image by digest so every rollout runs an exact, verified image — this is the ONE enterprise pin style; nowhere on this page does an enterprise command set image.tag.

If your GitLab or license server presents a certificate from an internal or corporate authority, add a caBundle block (see the chart's bundled values.yaml for its keys) — left at its defaults, nothing extra is mounted.

Install

  1. Create the namespace.

    bash
    kubectl create namespace theoworks
    kubectl label namespace theoworks pod-security.kubernetes.io/enforce=restricted
  2. Switch to it (this is what oc project does on OpenShift -- no command below needs a namespace flag):

    bash
    kubectl config set-context --current --namespace=theoworks
  3. Set these once:

    bash
    export THEOWORKS_VERSION=X.Y.Z                        # Enterprise -- the version you're installing (the connected-cluster card, or the bundle filenames you downloaded)
    export REGISTRY=your-registry.example.internal        # Enterprise, air-gapped cluster only -- where you load and pull the image from
  4. Only if you chose "You have a certificate": create the TLS Secret (after the namespace switch, so it lands in the right namespace):

    bash
    kubectl create secret tls theoworks-tls --cert=tls.crt --key=tls.key

Free or Pro

bash
helm install theoworks oci://public.ecr.aws/d9m2o0n8/chart/theoworks \
  -f values.yaml

Enterprise, air-gapped cluster

Verify and load the image into your own registry first, then install from the local chart file:

bash
sha256sum -c checksums.txt

mkdir -p theoworks-app-enterprise-$THEOWORKS_VERSION-oci
tar -xf theoworks-app-enterprise-$THEOWORKS_VERSION.oci.tar -C theoworks-app-enterprise-$THEOWORKS_VERSION-oci
cosign load --dir theoworks-app-enterprise-$THEOWORKS_VERSION-oci $REGISTRY/theoworks-app-enterprise

DIGEST=$(jq -r '.manifests[] | select(.annotations.kind == "dev.cosignproject.cosign/imageIndex") | .digest' theoworks-app-enterprise-$THEOWORKS_VERSION-oci/index.json)
cosign verify --key cosign.pub --insecure-ignore-tlog \
  $REGISTRY/theoworks-app-enterprise@$DIGEST

cosign load takes the image reference positionally (there is no --registry flag); the digest to verify -- and to pin image.digest below -- comes from the extracted OCI layout's index.json, never from anything cosign load prints.

Then install:

bash
helm install theoworks ./theoworks-enterprise-$THEOWORKS_VERSION.tgz \
  --set image.registry=$REGISTRY \
  --set image.digest=$DIGEST \
  -f values.yaml

Enterprise, connected cluster

Sign in to the registry first (the credential's password is meant for helm registry login's interactive prompt — never a command-line flag, never a file):

bash
helm registry login 366258937776.dkr.ecr.eu-central-1.amazonaws.com --username AWS

Paste the password when prompted, then create the image pull secret (the kubelet needs its own -- helm registry login above only covers the chart pull). The sequence below never puts the password on the command line, into shell history, or into a file:

bash
printf 'Paste the registry password, then press Enter: '
IFS= read -rs TW_PW; echo
TW_AUTH=$(printf 'AWS:%s' "$TW_PW" | base64 | tr -d '\n')
TW_CFG=$(printf '{"auths":{"%s":{"username":"AWS","password":"%s","auth":"%s"}}}' \
  366258937776.dkr.ecr.eu-central-1.amazonaws.com "$TW_PW" "$TW_AUTH" | base64 | tr -d '\n')
printf '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"theoworks-pull"},"type":"kubernetes.io/dockerconfigjson","data":{".dockerconfigjson":"%s"}}' "$TW_CFG" \
  | kubectl apply --server-side -f -
unset TW_PW TW_AUTH TW_CFG

Name it in your values with image.pullSecrets: ["theoworks-pull"] (already shown above), then install:

bash
helm install theoworks oci://366258937776.dkr.ecr.eu-central-1.amazonaws.com/charts/theoworks-enterprise \
  --version "$THEOWORKS_VERSION" \
  -f values.yaml

End the registry session:

bash
helm registry logout 366258937776.dkr.ecr.eu-central-1.amazonaws.com

A registry credential expires (at most 12 hours). Recommended: mirror the image into your own registry once (see Day-2) rather than depending on it for every future pull, drain, or scale-out.

Verify

bash
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/instance=theoworks --timeout=10m
kubectl get ingress

The HOSTS column shows your hostname and ADDRESS is filled in. Then confirm the instance is healthy and reachable: open your hostname, sign in through your GitLab, open a document, edit it, and save -- the commit lands in your GitLab repository.

Note

Helm's post-install message currently shows an oc command -- on Kubernetes use the kubectl wait command above instead.

If something's wrong

| Symptom | Fix | |---|---| | Install stops with "forwardedAllowIps is REQUIRED" | Set forwardedAllowIps -- see Find your forwardedAllowIps | | Install stops with "no matches for kind Route" — Helm's exact message: no matches for kind "Route" in version "route.openshift.io/v1" | You're using the OpenShift values. Set route.enabled: false and use the Kubernetes values file above | | Your hostname returns the controller's "404 page not found" | The request's hostname doesn't match ingress.host. Check DNS and ingress.host |

License and usage

  • Pro: sign in as the maintainer and open Settings ▸ Usage & License. Paste the code from your purchase email into Redemption code, then select Activate Pro. The same page shows current usage against your plan's seats.

  • Enterprise: your license file is stored in a Kubernetes Secret: the one named in license.existingSecret (key license.json by default). To apply a renewed license, update that Secret in place. You don't need helm upgrade or a restart:

    bash
    kubectl create secret generic theoworks-license \
      --from-file=license.json=license.json \
      --dry-run=client -o yaml | kubectl apply -f -

    TheoWorks reads the license file every time it checks your license. So the new license takes effect once Kubernetes updates the mounted file, which can take a short while. To confirm, open Settings ▸ Usage & License: it shows Licensed · Enterprise while a valid license is in effect.

    If you put the license inline with license.content instead, replace it in your values file and run helm upgrade (see Day-2 operations).

Day-2 operations

Both the CA bundle and the license are mounted as files -- rotate either by updating the underlying secret in place; the mounted file refreshes without a restart.

Free or Pro: the public chart already bakes the current release's image tag, so a plain upgrade picks up the latest release -- you don't need to set image.tag yourself (doing so overrides the chart's baked tag):

bash
helm upgrade theoworks oci://public.ecr.aws/d9m2o0n8/chart/theoworks \
  -f values.yaml

Enterprise, connected cluster: the chart bakes the released image's digest itself, so an upgrade never sets image.digest -- it's the same install command from Install, run again with the new version.

Already running from your own registry? Use Upgrade a mirrored install instead.

First, get a fresh registry credential. The one from your install has expired (registry credentials last at most 12 hours). At theoworks.io/download, in the Connected cluster card, select Get a registry credential (or Get a new credential). Keep that page open: you'll paste its password below. Finish these steps before the time shown on the card.

bash
helm registry login 366258937776.dkr.ecr.eu-central-1.amazonaws.com --username AWS

Refresh the image pull secret so your cluster can pull the new version's image, with the same password-safe sequence Install used:

bash
printf 'Paste the registry password, then press Enter: '
IFS= read -rs TW_PW; echo
TW_AUTH=$(printf 'AWS:%s' "$TW_PW" | base64 | tr -d '\n')
TW_CFG=$(printf '{"auths":{"%s":{"username":"AWS","password":"%s","auth":"%s"}}}' \
  366258937776.dkr.ecr.eu-central-1.amazonaws.com "$TW_PW" "$TW_AUTH" | base64 | tr -d '\n')
printf '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"theoworks-pull"},"type":"kubernetes.io/dockerconfigjson","data":{".dockerconfigjson":"%s"}}' "$TW_CFG" \
  | kubectl apply --server-side -f -
unset TW_PW TW_AUTH TW_CFG
bash
export THEOWORKS_VERSION=X.Y.Z                        # the new release
helm upgrade theoworks oci://366258937776.dkr.ecr.eu-central-1.amazonaws.com/charts/theoworks-enterprise \
  --version "$THEOWORKS_VERSION" \
  -f values.yaml
bash
helm registry logout 366258937776.dkr.ecr.eu-central-1.amazonaws.com

The pull secret now holds today's credential, which stops working within 12 hours. Pods that are already running aren't affected. But if Kubernetes later moves the pod to a node that doesn't have the image yet, pulling the image fails. To stop depending on this credential, mirror the image into your own registry. After that, an upgrade only needs a fresh credential to fetch the new chart and copy the new image; your running deployment never does.

Enterprise, air-gapped cluster: download and verify the new bundle first -- re-run the air-gapped verify-and-load sequence against it (which reads the new $DIGEST from its index.json) -- then upgrade from the new chart file with the new registry and digest:

bash
export THEOWORKS_VERSION=X.Y.Z                        # the new release
helm upgrade theoworks ./theoworks-enterprise-$THEOWORKS_VERSION.tgz \
  --set image.registry=$REGISTRY \
  --set image.digest=$DIGEST \
  -f values.yaml

On shutdown the pod stops accepting new traffic first and finishes in-flight edits and saves within its shutdown grace period. Because this baseline runs a single pod, plan an upgrade for a quiet period -- multi-pod, continuously-available upgrades are on the roadmap.

Mirror the image for a connected cluster

A registry credential is short-lived. If your cluster's pod may reschedule after the credential expires (a redeploy, a node drain), pull secrets created from it will fail the next image pull. Mirror the image into your own internal registry so the running deployment no longer depends on the credential.

First, get a fresh registry credential. The one from your install has expired (registry credentials last at most 12 hours). At theoworks.io/download, in the Connected cluster card, select Get a registry credential (or Get a new credential). Keep that page open: you'll paste its password below. Finish these steps before the time shown on the card.

Set these once:

bash
export INTERNAL_REGISTRY=your-internal-registry.example.internal
export THEOWORKS_VERSION=X.Y.Z                        # the version you run now: the number after "theoworks-enterprise-" in `helm list`

Sign in to copy the image:

bash
skopeo login 366258937776.dkr.ecr.eu-central-1.amazonaws.com --username AWS

If your own registry needs a sign-in to push, run skopeo login "$INTERNAL_REGISTRY" too.

Copy the image you run now into your registry. It is tagged with its version, and its digest stays exactly the same:

bash
IMAGE_DIGEST=$(kubectl get pod -l app.kubernetes.io/instance=theoworks -o jsonpath='{.items[0].spec.containers[0].image}' | sed 's/.*@//')
skopeo copy --all --preserve-digests \
  docker://366258937776.dkr.ecr.eu-central-1.amazonaws.com/theoworks-app-enterprise@$IMAGE_DIGEST \
  docker://$INTERNAL_REGISTRY/theoworks-app-enterprise:$THEOWORKS_VERSION
skopeo inspect --raw docker://$INTERNAL_REGISTRY/theoworks-app-enterprise@$IMAGE_DIGEST > /dev/null && echo "Your registry serves the image by digest."
skopeo logout 366258937776.dkr.ecr.eu-central-1.amazonaws.com

Copy to a version tag: some registries, OpenShift's own among them, don't keep an image pushed without a tag. TheoWorks still pulls it by digest, and the last check confirms your registry serves it that way. If the check prints nothing, stop: your registry isn't serving the image yet.

Let your cluster sign in to your registry. If your cluster can pull from your registry without a sign-in, skip this step. If your platform team already gave you a pull secret for it in this namespace, skip it too, and use that secret's name below. Otherwise create one (you'll be asked for your registry's username and password):

bash
TW_AUTHDIR=$(mktemp -d)
skopeo login --authfile "$TW_AUTHDIR/auth.json" "${INTERNAL_REGISTRY%%/*}"
kubectl create secret generic internal-pull \
  --type=kubernetes.io/dockerconfigjson \
  --from-file=.dockerconfigjson="$TW_AUTHDIR/auth.json" \
  --dry-run=client -o yaml | kubectl apply --server-side -f -
rm -rf "$TW_AUTHDIR"; unset TW_AUTHDIR

This path is safe for any password: skopeo writes the sign-in itself. The sign-in sits for a moment in a private temporary folder (mktemp -d, readable only by you), and the last line deletes it.

Point the chart at your registry. In your values.yaml, under image:, add registry: with your registry's address (the same value as $INTERNAL_REGISTRY), and add your registry's pull secret to image.pullSecrets next to theoworks-pull, for example pullSecrets: ["theoworks-pull", "internal-pull"]. Skip the pull secret if the previous step didn't apply. Don't change the digest; the chart keeps it.

Apply it. Sign in again to fetch the chart: Helm keeps its own sign-in. Paste the same password, or get a new credential first if the time shown on the card has passed. Use helm upgrade, not helm install, which fails on an existing release.

bash
helm registry login 366258937776.dkr.ecr.eu-central-1.amazonaws.com --username AWS
helm upgrade theoworks oci://366258937776.dkr.ecr.eu-central-1.amazonaws.com/charts/theoworks-enterprise \
  --version "$THEOWORKS_VERSION" \
  -f values.yaml
helm registry logout 366258937776.dkr.ecr.eu-central-1.amazonaws.com

Once the pod runs from your registry, theoworks-pull is no longer used, and it holds a credential that expires. At your next upgrade you can remove it from image.pullSecrets. From now on, upgrade with Upgrade a mirrored install (below), not the connected-cluster upgrade above.

Upgrade a mirrored install

Once your cluster runs from your own registry, copy each new version's image there first, then upgrade. Only these steps need a TheoWorks sign-in; your running deployment never does.

First, get a fresh registry credential. The one from your install has expired (registry credentials last at most 12 hours). At theoworks.io/download, in the Connected cluster card, select Get a registry credential (or Get a new credential). Keep that page open: you'll paste its password below. Finish these steps before the time shown on the card.

Set these once, and sign in to fetch the new chart:

bash
export THEOWORKS_VERSION=X.Y.Z                        # the new release
export INTERNAL_REGISTRY=your-internal-registry.example.internal
helm registry login 366258937776.dkr.ecr.eu-central-1.amazonaws.com --username AWS

Paste the password when prompted.

Find the new version's image, read from the new chart, never from the running pod:

bash
NEW_DIGEST=$(helm template theoworks oci://366258937776.dkr.ecr.eu-central-1.amazonaws.com/charts/theoworks-enterprise \
  --version "$THEOWORKS_VERSION" -f values.yaml \
  | grep -o 'theoworks-app-enterprise@sha256:[0-9a-f]*' | head -n 1 | cut -d@ -f2)
echo "New image digest: $NEW_DIGEST"

Helm may also print its own Pulled: and Digest: lines for the chart; ignore them. The line that matters is the one starting New image digest:. If it shows nothing after the colon, stop and check the version number.

Copy it into your registry:

bash
skopeo login 366258937776.dkr.ecr.eu-central-1.amazonaws.com --username AWS
skopeo copy --all --preserve-digests \
  docker://366258937776.dkr.ecr.eu-central-1.amazonaws.com/theoworks-app-enterprise@$NEW_DIGEST \
  docker://$INTERNAL_REGISTRY/theoworks-app-enterprise:$THEOWORKS_VERSION
skopeo inspect --raw docker://$INTERNAL_REGISTRY/theoworks-app-enterprise@$NEW_DIGEST > /dev/null && echo "Your registry serves the new image by digest."
skopeo logout 366258937776.dkr.ecr.eu-central-1.amazonaws.com

This uses the same password. If your own registry needs a sign-in to push, run skopeo login "$INTERNAL_REGISTRY" too. If the check prints nothing, stop before upgrading.

Upgrade:

bash
helm upgrade theoworks oci://366258937776.dkr.ecr.eu-central-1.amazonaws.com/charts/theoworks-enterprise \
  --version "$THEOWORKS_VERSION" \
  -f values.yaml
helm registry logout 366258937776.dkr.ecr.eu-central-1.amazonaws.com

Your values.yaml already points at your registry and its pull secret, so the new pod pulls the new image from there.

Reference

| Setting | What it does | Default | |---|---|---| | ingress.enabled | Turn on the plain-Kubernetes Ingress | false | | ingress.className | Your cluster's IngressClass | "" | | ingress.host | External hostname | "" | | ingress.tlsStrategy | own-cert, acme, or none | none | | ingress.tlsSecretName | The TLS Secret holding your certificate | "" | | forwardedAllowIps | Ingress source CIDR (required); route.forwardedAllowIps wins if both are set | "" | | podSecurityContext.runAsUser / .runAsGroup / .fsGroup | Pod identity Kubernetes doesn't assign automatically | 10001 / 0 / 0 | | persistence.index.size / persistence.state.size | Volume sizes | 2Gi / 256Mi | | image.registry / image.repository | Where the image comes from | (enterprise defaults; Free/Pro overrides) | | image.tag | Override to pin an exact Free/Pro build | (chart default is the current release -- optional, not required) | | image.digest | Exact image for Enterprise (pin by digest) | (required for Enterprise) | | image.pullSecrets | Registry pull secret name(s); none needed for Free/Pro | [] | | license.enabled | Mount an offline license (true Enterprise; false Free/Pro) | true | | config.gitlabInstance / oauthRedirectUri | Your GitLab URL and OAuth redirect | (none) | | config.maintainerEmail | Optional -- first sign-in from this address becomes the maintainer | "" | | secrets.oauthClientId / oauthClientSecret / sessionKey | GitLab OAuth app credentials + session key | (none) | | license.existingSecret / content | Your offline license (Enterprise) | (none) | | resources.limits | CPU / memory ceiling | cpu: 2, memory: 2Gi |

See the chart's bundled values.yaml and values-kubernetes.yaml for every setting and inline guidance.

Next steps