Skip to content

Helm Chart Deployment ​

This repository ships two Helm charts, for two different deployment models:

ChartPathWorkload kindUse when
emgrhelm/emgr/Standard Kubernetes Deployment (via the bjw-s common library chart)You want a normal, always-on Deployment - fixed replicaCount, an optional HPA, a PersistentVolumeClaim for local_fs storage.
emgr-serverlesshelm/serverless/Knative Service (via the Bitnami common library chart)You run Knative and want scale-to-zero, and S3/MinIO storage (no local disk needed).

Both charts install emgr itself; pick one, not both, for a given deployment.

Installing a published chart ​

Both charts are published two ways; pick whichever your tooling prefers. Row one (chart-releaser) has published every version below since 2025. Row two (OCI, signed) is new: it publishes on the next vX.Y.Z release tag whose chart version: has moved, not retroactively for versions already on the classic repository.

ReferenceSignedSource
Classic Helm repositoryhttps://vaam-apps.github.io/image-resizer/No.github/workflows/deploy-docs.yml's deploy-helm job (helm/chart-releaser-action), on every push to main where a chart's own version: moved.
OCI registryoci://ghcr.io/vaam-apps/charts/emgr and oci://ghcr.io/vaam-apps/charts/emgr-serverlessYes (cosign, keyless).github/workflows/release.yml, on a vX.Y.Z release tag, again only when a chart's own version: moved since its last publish.

Both track each chart's hand-managed version: field (Chart.yaml), not the application's own release number (appVersion:) - see .github/workflows/release-please.yml's header for why the two are deliberately independent. A given version: therefore lands in both places or neither; there is no release where one has it and the other doesn't.

bash
# Classic repository
helm repo add image-resizer https://vaam-apps.github.io/image-resizer/
helm repo update
helm install emgr image-resizer/emgr --namespace emgr --create-namespace

# OCI registry - no `helm repo add` needed. `CHART_VERSION` is
# `helm/emgr/Chart.yaml`'s own `version:` at the release you want, not the
# application's `vX.Y.Z` tag - see the table above for why the two differ.
CHART_VERSION=0.1.9
IMAGE=ghcr.io/vaam-apps/charts/emgr:$CHART_VERSION

# Optional, needs `cosign` and network access to sigstore's
# infrastructure. Read the output, not just the exit code - the identity
# below only matches a chart this repository's own release.yml built and
# signed; the same caveat vpay's own release runbook gives applies here
# too, unread until someone actually reads a `Subject`/`githubWorkflowRef`
# claim off a real signature.
cosign verify \
  --certificate-identity-regexp '^https://github\.com/vaam-apps/image-resizer/\.github/workflows/release\.yml@refs/tags/v.*$' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  "$IMAGE"

helm install emgr oci://ghcr.io/vaam-apps/charts/emgr --version "$CHART_VERSION" \
  --namespace emgr --create-namespace

emgr-serverless installs the identical way, substituting that chart's own name, OCI path and Chart.yaml from the table above.

Prerequisites ​

  • A Kubernetes cluster and Helm 3.
  • For emgr-serverless: Knative Serving installed on the cluster, and cert-manager if you enable domain.tls.issuerRef.

emgr (Deployment chart) ​

Chart location ​

helm/emgr/. Chart.yaml pins its common dependency to bjw-s's library chart 4.0.1 (see helm/emgr/Chart.lock) - most of the templating (Deployment, Service, Ingress, ConfigMap, HPA, PVC) comes from that library via templates/common.yaml's single {{ include "bjw-s.common.loader.all" $ }}. Two plain templates are layered on top:

  • templates/poddisruptionbudget.yaml - a PodDisruptionBudget with minAvailable: 1, added because the pinned 4.0.1 library predates that chart's own podDisruptionBudget values key. Without it, a voluntary disruption (node drain, cluster upgrade) could evict every main replica at once.
  • The probes under controllers.main.containers.app.probes in values.yaml - the bjw-s common library does not synthesize liveness/readiness/startup probe defaults, so without this block traffic would route to pods before storage finishes initializing, and a wedged container would never be restarted.

Probes ​

All three probes are plain Kubernetes HTTP probes against /health on port 3000 (values.yaml, controllers.main.containers.app.probes):

ProbeinitialDelayperiodtimeoutfailureThreshold
liveness5s10s2s3
readiness5s10s2s3
startup0s5s-30 (150s total before the container is killed for never finishing startup)

What the chart configures out of the box ​

values.yaml's configMaps.config.data sets exactly three variables:

yaml
LOCAL_FS_STORAGE_PATH: /tmp/data/images
CDN_BASE_URL: https://emgr.com
PERFORMANCE_PROFILE: high_throughput

...loaded via envFrom: configMapRef on the app container. A permission-fix init container (running as root) chowns/chmods the mounted ReadWriteMany PVC to uid/gid 1001 before the app container (which runs as that non-root user, defaultPodOptions.securityContext) starts.

Signing / metrics-auth secrets (GH #84) ​

emgr fails closed at startup without SIGNING_KEY/SIGNING_SALT (or ALLOW_UNSIGNED_REQUESTS), and without METRICS_AUTH_TOKEN (or ALLOW_UNAUTHENTICATED_METRICS) on an otel-enabled image (see Installation).

This is a real cluster, not a local compose.yaml, so the chart does not default ALLOW_UNSIGNED_REQUESTS/ALLOW_UNAUTHENTICATED_METRICS to true the way compose.yaml does for local development - that would silently trade security for convenience. Instead values.yaml's controllers.main.containers.app.env already wires all three variables to a Secret named emgr-signing:

yaml
env:
  SIGNING_KEY:
    valueFrom:
      secretKeyRef: { name: emgr-signing, key: signing-key }
  SIGNING_SALT:
    valueFrom:
      secretKeyRef: { name: emgr-signing, key: signing-salt }
  METRICS_AUTH_TOKEN:
    valueFrom:
      secretKeyRef: { name: emgr-signing, key: metrics-auth-token }

You must create that Secret yourself - the chart deliberately does not, so the key/salt/token never live in values.yaml or a values override:

bash
kubectl create secret generic emgr-signing --namespace emgr \
  --from-literal=signing-key="$(openssl rand -hex 32)" \
  --from-literal=signing-salt="$(openssl rand -hex 16)" \
  --from-literal=metrics-auth-token="$(openssl rand -hex 32)"

If the Secret is missing, the pod fails loudly (CreateContainerConfigError, visible in kubectl describe pod) instead of starting insecurely. To point at a differently-named Secret, override the name: fields above in your own values file. See the bjw-s common library's env/envFrom docs for the full schema. METRICS_AUTH_TOKEN only matters once the image itself is an otel-enabled build - see the image tag note below.

Image tag ​

values.yaml's global.version defaults to fs-latest - a floating tag (moves on every push to main; .github/workflows/build.yml has no release-tag trigger, so there's no immutable semver tag to default to instead). fs-latest is a local_fs-only, non-otel build, so METRICS_AUTH_TOKEN doesn't apply unless you override global.version to one of the *_otel-* flavours (see Docker deployment for the full tag list). Pin global.version to a specific fs-<sha> (or fs_otel-<sha>) tag for anything beyond a quick smoke test - pullPolicy: Always on the container is a direct consequence of the default floating tag, so different pods don't silently end up running different builds under the same tag name.

Deployment steps ​

bash
# From the local chart directory - see "Installing a published chart" above for the packaged alternative
helm dependency build ./helm/emgr

# Create the emgr-signing Secret first (see above) - the chart's values.yaml
# already points at it, no values override needed unless you want to
# rename the Secret or its keys.
helm install emgr ./helm/emgr --namespace emgr --create-namespace

kubectl get pods -n emgr
kubectl get svc -n emgr

Upgrade / uninstall:

bash
helm upgrade emgr ./helm/emgr --namespace emgr
helm uninstall emgr --namespace emgr

emgr-serverless (Knative chart) ​

Chart location ​

helm/serverless/, depending on Bitnami's common library chart (Chart.yaml; pulled from oci://registry-1.docker.io/bitnamicharts). templates/knative-service.yaml renders a single serving.knative.dev/v1 Service; templates/kserving-domain-mapping.yaml optionally adds a Knative DomainMapping (and a cert-manager Certificate, if domain.tls.issuerRef is set) when domain.enabled: true.

Probes ​

Unlike the Deployment chart, these are Knative-native probes (still plain httpGet on /health, port 3000 from ports.http1.port) - readinessProbe, livenessProbe and startupProbe, each with no explicit timing overrides, so Knative's own probe defaults apply.

What the chart configures out of the box ​

values.yaml's env map is wired for S3/MinIO storage, with the MinIO credentials already sourced from a Secret (name minio, keys access-key/secret-key) via secretKeyRef - the same pattern you'd use to add signing/metrics-auth secrets, see below:

yaml
env:
  MINIO_ACCESS_KEY_ID: { secretKeyRef: { name: minio, key: access-key } }
  MINIO_SECRET_ACCESS_KEY: { secretKeyRef: { name: minio, key: secret-key } }
  MINIO_ENDPOINT_URL: http://minio:9000
  MINIO_BUCKET: emgr
  # ...

Signing / metrics-auth secrets (GH #84) ​

Exactly like the emgr chart above, values.yaml's env map already wires SIGNING_KEY/SIGNING_SALT/METRICS_AUTH_TOKEN to a Secret named emgr-signing, using the same secretKeyRef shape as MINIO_ACCESS_KEY_ID above:

yaml
env:
  SIGNING_KEY: { secretKeyRef: { name: emgr-signing, key: signing-key } }
  SIGNING_SALT: { secretKeyRef: { name: emgr-signing, key: signing-salt } }
  METRICS_AUTH_TOKEN:
    { secretKeyRef: { name: emgr-signing, key: metrics-auth-token } }

Create that Secret yourself before installing (see the kubectl create secret command in the emgr chart section above - the same Secret works for both charts). Without it, the Knative revision fails its startup probe and never goes Ready, instead of serving unsigned requests or an open /metrics.

Image tag and registry (GH #85, fixed) ​

values.yaml's image.registry is empty and image.repository carries the full ghcr.io/vaam-store/image-resizer path - helm template used to render a doubled ghcr.io/ghcr.io/... reference when both fields set a registry host; only repository does now. image.tag defaults to s3_otel-latest, matching this chart's env defaults (S3/MinIO storage, OTLP_* variables meaningful only on an otel build) and an image flavour .github/workflows/build.yml actually publishes (see Docker deployment for the full tag list). Like helm/emgr's global.version, this is still a floating tag - pin to a specific s3_otel-<sha> for anything beyond a quick smoke test.

values.yaml's env.LOG_LEVEL is a plain info - it used to be Docker Compose's ${LOG_LEVEL:-info} shell-substitution syntax, which Helm never evaluates and rendered as that literal string. Override env.LOG_LEVEL directly (--set env.LOG_LEVEL=debug) if you want something else.

Deployment steps ​

bash
helm dependency build ./helm/serverless

# Create the emgr-signing Secret first (see above).
helm install emgr-serverless ./helm/serverless --namespace emgr --create-namespace

kubectl get ksvc -n emgr

Upgrade / uninstall:

bash
helm upgrade emgr-serverless ./helm/serverless --namespace emgr
helm uninstall emgr-serverless --namespace emgr

Notes ​

  • Both charts are published two ways - see Installing a published chart above. The commands in each chart's own "Deployment steps" section install from the local helm/emgr/helm/serverless directories in this checkout instead (helm dependency build first, to fetch the pinned common library chart into charts/), which is the right choice when you're changing the chart itself, not just deploying it.
  • Configuration beyond what's listed above (performance tuning, the SSRF guard, resolution limits, presets) works the same way: add the env var from Configuration to whichever chart's env mechanism you're using.
  • .github/workflows/ci.yml's helm-verify job runs helm dependency build, helm lint and helm template for both charts on every PR (GH #84 / #85) - a rendering defect like the ones described above now fails a build instead of reaching a user.

Released under the MIT License.