k8s-container-monitor-helm: Helm Charts for the Container Monitor
This post covers k8s-container-monitor-helm, a companion repo that packages the exporter and a private in-cluster registry as Helm charts. It picks up where the previous post left off.
The problem
The previous post covered building the exporter and running it locally. That’s enough for development. Deploying to a Kubernetes cluster adds two requirements.
First, the image needs somewhere to live that the cluster can pull from. Pushing to a public registry works, but adds an external dependency and an authentication step on every pull. An in-cluster private registry keeps pulls local and removes the dependency.
Second, config and credentials need to be injected as Kubernetes Secrets and ConfigMaps rather than environment variables set by hand.
k8s-container-monitor-helm packages both: a registry chart that deploys the image store,
and a container-monitor chart that deploys the exporter. A justfile wires them together.
Two-chart layout
Two charts, two namespaces:
namespace: registry
+--------------------------+
| registry chart |
| docker-registry or zot |
| NodePort 30500 |
+----------+---------------+
| in-cluster image pull
| registry.registry.svc.cluster.local:5000
v
namespace: monitoring
+--------------------------+
| container-monitor chart |
| Deployment + ClusterIP |
| ConfigMap: config |
| Secret: credentials |
+--------------------------+
The address shown uses port 5000 (docker-registry default). When using zot, the port is 5080 — update image.repository accordingly.
The registry chart deploys the image store in the registry namespace and exposes it as a
NodePort on 30500 for pushing from outside the cluster. The container-monitor chart deploys
the exporter in the monitoring namespace, pulling its image from the in-cluster service
address registry.registry.svc.cluster.local:5000.
No cross-namespace RBAC is needed. docker-registry accepts unauthenticated pulls by default.
Registry choice
The registry chart supports two backends, selected by registry.type in values:
docker-registry | zot | |
|---|---|---|
| Image | registry:2 | ghcr.io/project-zot/zot-linux-amd64 |
| Port | 5000 | 5080 |
| Auth | None by default | Configurable |
| Persistence | Optional | Recommended |
| OCI spec | Partial | Full (1.1.0) |
| Service type | NodePort | ClusterIP |
| When to use | Colima, local dev | Production cluster |
docker-registry is the default. Switching to zot requires one values change:
registry:
type: zot
But the port changes from 5000 to 5080 — if you switch to zot,
update image.repository in the container-monitor chart to use port 5080:
image:
repository: registry.registry.svc.cluster.local:5080/container-monitor-go
tag: latest
Tutorial: Deploy on Colima
Prerequisites
- Colima installed
- Helm 3, kubectl, just, Docker
1. Start Colima with Kubernetes
just colima-start
Runs colima start --kubernetes --cpu 2 --memory 4, starting k3s inside the Colima VM.
2. Configure the insecure registry
just colima-configure
colima stop && just colima-start
This writes /etc/rancher/k3s/registries.yaml inside the Colima VM, configuring k3s to allow
unauthenticated HTTP pulls from localhost:30500. Without this, image pulls fail with a TLS
error — k3s rejects plain HTTP from any address unless it is explicitly listed as a mirror.
mirrors:
"localhost:30500":
endpoint:
- "http://localhost:30500"
The restart is required. k3s reads registries.yaml only at startup.
3. Build the app image
just build
Runs just docker-build in the k8s-container-monitor-go directory (configurable via APP_DIR
env var). Produces a local image tagged container-monitor-go:local.
4. Install the registry and push
just install-registry
just push
install-registry deploys the registry chart and waits for the pod to be ready. push tags
the local image as localhost:30500/container-monitor-go:latest and pushes it through the
NodePort.
5. Configure and install the monitor
Edit charts/container-monitor/values.yaml with the hosts and registries to monitor, then:
just install-monitor
Minimal config:
config:
websites:
hosts:
- example.com
artifacts:
catalog:
url: git/your-catalog-repo
registry:
prod:
host: yourregistry.example.com
secrets:
catalogToken: "ghp_yourtoken"
registries:
prod:
user: "robot$account"
token: "yourtoken"
Registry config keys (prod above) are uppercased to build env var names — prod becomes
PROD_USER and PROD_TOKEN. Keys with dots produce invalid env var names that Kubernetes
rejects. Use underscores or single words.
6. Verify
just status # pods and services in both namespaces
just check-metrics # port-forward to :8000 and curl /metrics
check-metrics port-forwards the monitor service, curls /metrics, and prints the first 40
lines. You should see status_code, loading_time, and missing_artifacts metrics.
Moving to a real cluster
Switch to zot and enable persistence. A values-prod.yaml is included in the registry chart:
# charts/registry/values-prod.yaml
registry:
type: zot
persistence:
enabled: true
size: 50Gi
storageClass: standard
service:
type: ClusterIP
Install the registry with the prod values:
helm upgrade --install registry charts/registry \
--namespace registry --create-namespace \
-f charts/registry/values-prod.yaml
Update image.repository in the container-monitor values to the cluster’s registry service
address, using port 5080 for zot:
image:
repository: registry.registry.svc.cluster.local:5080/container-monitor-go
tag: latest
Then install the monitor:
helm upgrade --install monitor charts/container-monitor \
--namespace monitoring --create-namespace \
-f your-values.yaml
No template changes are needed in either chart — the only differences from the Colima setup
are registry.type, persistence.enabled, and the port in image.repository.
What’s next
- Prometheus ServiceMonitor — the monitor exposes
/metricson port 8000. A ServiceMonitor CR would let Prometheus scrape it automatically without a manual port-forward. - Grafana dashboard — panels for
missing_artifacts(gauge by registry) andstatus_code(status by host) would surface the data without querying the endpoint directly.