changedetection.io
changedetection.io watches URLs for content changes, stores snapshots and diff history, and sends notifications through
Apprise-backed channels such as email, webhooks, Slack, Telegram, Discord, Gotify, and many others. The HelmForge chart
packages the official ghcr.io/dgtlmoon/changedetection.io:0.55.7 image with persistent /datastore storage and an
optional Playwright browser sidecar for JavaScript-rendered pages.
Architecture
The chart deploys a single Deployment with the Recreate strategy. The application stores its SQLite database,
watch configuration, rendered snapshots, and optional Python user packages under /datastore, which is backed by a PVC
by default.
| Component | Purpose |
|---|---|
changedetection container |
Runs the changedetection.io web UI, scheduler, diff engine, and notification flow. |
Optional browser sidecar |
Runs Browserless Chromium on localhost for Playwright-backed JavaScript rendering. |
| PVC | Stores SQLite data, snapshots, watch history, and Python user package installs. |
| Service | Exposes the HTTP application port inside the cluster. |
| Ingress or HTTPRoute | Optional external routing, disabled by default. |
| ExternalSecret | Optional environment Secret materialization for notification credentials or runtime tuning. |
The most important sizing decision is whether browser.enabled is required. Static pages and APIs work without the
sidecar. SPAs, modern commerce pages, and client-rendered dashboards usually need Playwright, which materially
increases CPU and memory usage.
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install changedetection helmforge/changedetection
OCI registry:
helm install changedetection oci://ghcr.io/helmforgedev/helm/changedetection
Port-forward a default install:
kubectl port-forward svc/changedetection 5000:80
Open http://localhost:5000/.
Deployment Examples
changedetection:
baseUrl: 'https://changes.example.com'
timezone: 'UTC'
persistence:
enabled: true
size: 10Gichangedetection:
baseUrl: 'https://changes.example.com'
fetchWorkers: 4
browser:
enabled: true
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 2Gi
persistence:
enabled: true
size: 20Gichangedetection:
baseUrl: 'https://changes.example.com'
fetchWorkers: 4
minimumSecondsRecheckTime: '300'
timezone: 'America/Sao_Paulo'
envFrom:
- secretRef:
name: changedetection-env
browser:
enabled: true
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 2Gi
persistence:
enabled: true
size: 50Gi
storageClass: fast-retain
ingress:
enabled: true
ingressClassName: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: changes.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- changes.example.com
secretName: changedetection-tls
externalSecrets:
enabled: true
secretStoreRef:
name: cluster-secrets
kind: ClusterSecretStore
target:
name: changedetection-env
creationPolicy: Owner
data:
- secretKey: LOGGER_LEVEL
remoteRef:
key: changedetection/app
property: loggerLevel
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gichangedetection:
baseUrl: 'https://changes.example.com'
gateway:
enabled: true
parentRefs:
- name: shared-gateway
namespace: ingress
hostnames:
- changes.example.comOperational Guidance
Keep persistence.enabled=true for any environment where watches or history matter. changedetection.io uses SQLite,
so the chart intentionally runs a single replica and does not expose horizontal scaling knobs. Increase persistence.size
for frequent checks, long retention, rendered screenshots, and many watches.
Enable browser.enabled=true only for pages that require JavaScript rendering. When enabled, reduce
changedetection.fetchWorkers and set browser.resources, because every concurrent browser-backed fetch can hold a
Chromium process in memory.
Set changedetection.baseUrl when exposing the service publicly. Notification links depend on this value and should
match the Ingress or Gateway hostname.
Use externalSecrets for notification credentials, application tuning values, or other upstream-supported environment
variables that should not live in Git. The rendered Secret is automatically consumed through envFrom when
externalSecrets.enabled=true.
Routing
Ingress and Gateway API are both disabled by default. Choose the routing model managed by your platform and avoid publishing the same hostname through both unless your cluster policy explicitly expects that topology.
Ingress supports class name, annotations, host paths, and TLS:
ingress:
enabled: true
ingressClassName: nginx
hosts:
- host: changes.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- changes.example.com
secretName: changedetection-tls
Gateway API renders an HTTPRoute and requires at least one gateway.parentRefs entry:
gateway:
enabled: true
parentRefs:
- name: shared-gateway
namespace: ingress
hostnames:
- changes.example.com
Service dual-stack fields are optional and preserve cluster defaults unless explicitly set:
service:
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4
- IPv6
Configuration Reference
Image And Application
| Parameter | Default | Description |
|---|---|---|
image.repository |
ghcr.io/dgtlmoon/changedetection.io |
Official changedetection.io image. |
image.tag |
0.55.7 |
Pinned upstream image tag. |
image.pullPolicy |
IfNotPresent |
Kubernetes image pull policy. |
changedetection.port |
5000 |
Container HTTP port. |
changedetection.baseUrl |
"" |
Public URL used in notification links. |
changedetection.fetchWorkers |
10 |
Concurrent fetch workers. Lower this when browser rendering is enabled. |
changedetection.minimumSecondsRecheckTime |
"" |
Minimum seconds between rechecks per watch. |
changedetection.timezone |
"" |
Container TZ value. |
changedetection.locale |
C |
LANG and LC_ALL locale. |
changedetection.defaultWatches.enabled |
false |
Allow upstream sample watches on a fresh datastore. |
changedetection.extraEnv |
[] |
Extra environment variables. |
changedetection.envFrom |
[] |
Additional envFrom sources. |
Browser
| Parameter | Default | Description |
|---|---|---|
browser.enabled |
false |
Enable the Browserless Chromium sidecar. |
browser.image.repository |
ghcr.io/browserless/chromium |
Browser sidecar image. |
browser.image.tag |
v2.46.0 |
Browser sidecar tag. |
browser.image.pullPolicy |
IfNotPresent |
Browser image pull policy. |
browser.resources |
{} |
Resource requests and limits for the sidecar only. |
Persistence
| Parameter | Default | Description |
|---|---|---|
persistence.enabled |
true |
Create/use persistent storage for /datastore. |
persistence.size |
10Gi |
PVC size. |
persistence.storageClass |
"" |
StorageClass name; empty uses cluster default. |
persistence.accessModes |
[ReadWriteOnce] |
PVC access modes. |
persistence.existingClaim |
"" |
Reuse an existing PVC. |
Routing And Service
| Parameter | Default | Description |
|---|---|---|
service.type |
ClusterIP |
Service type. |
service.port |
80 |
Service HTTP port. |
service.annotations |
{} |
Service annotations. |
service.ipFamilyPolicy |
unset | Optional Service IP family policy. |
service.ipFamilies |
[] |
Optional Service IP families. |
ingress.enabled |
false |
Render Ingress. |
ingress.ingressClassName |
traefik |
Ingress class name. |
ingress.annotations |
{} |
Ingress annotations. |
ingress.hosts |
[] |
Ingress host/path rules. |
ingress.tls |
[] |
Ingress TLS entries. |
gateway.enabled |
false |
Render Gateway API HTTPRoute. |
gateway.parentRefs |
[] |
Required parent Gateway references when enabled. |
gateway.hostnames |
[] |
HTTPRoute hostnames. |
gateway.path |
/ |
HTTPRoute path prefix. |
gateway.pathType |
PathPrefix |
HTTPRoute path match type. |
External Secrets
| Parameter | Default | Description |
|---|---|---|
externalSecrets.enabled |
false |
Render an ExternalSecret and consume the target Secret. |
externalSecrets.apiVersion |
external-secrets.io/v1 |
ExternalSecret API version. |
externalSecrets.refreshInterval |
1h |
Reconciliation interval. |
externalSecrets.secretStoreRef.name |
"" |
SecretStore or ClusterSecretStore name. |
externalSecrets.secretStoreRef.kind |
SecretStore |
Store reference kind. |
externalSecrets.target.name |
"" |
Target Secret name; empty derives from release name. |
externalSecrets.target.creationPolicy |
Owner |
ExternalSecret target creation policy. |
externalSecrets.data |
[] |
Individual remote key mappings. |
externalSecrets.dataFrom |
[] |
Provider-side extraction entries. |
Runtime, Security, And Scheduling
| Parameter | Default | Description |
|---|---|---|
probes.startup.enabled |
true |
Enable startup probe. |
probes.liveness.enabled |
true |
Enable liveness probe. |
probes.readiness.enabled |
true |
Enable readiness probe. |
resources.requests.cpu |
100m |
Main container CPU request. |
resources.requests.memory |
256Mi |
Main container memory request. |
resources.limits.cpu |
1000m |
Main container CPU limit. |
resources.limits.memory |
1Gi |
Main container memory limit. |
podSecurityContext.fsGroup |
1000 |
Writable group for /datastore. |
podSecurityContext.seccompProfile.type |
RuntimeDefault |
Pod seccomp profile. |
securityContext.runAsNonRoot |
true |
Run main container as non-root. |
securityContext.allowPrivilegeEscalation |
false |
Prevent privilege escalation. |
securityContext.readOnlyRootFilesystem |
false |
Writable root remains enabled for upstream runtime behavior. |
securityContext.capabilities.drop |
[ALL] |
Drop Linux capabilities. |
nodeSelector |
{} |
Node selector. |
tolerations |
[] |
Pod tolerations. |
affinity |
{} |
Pod affinity. |
topologySpreadConstraints |
[] |
Topology spread constraints. |
priorityClassName |
"" |
PriorityClass name. |
terminationGracePeriodSeconds |
30 |
Pod termination grace period. |
extraVolumes |
[] |
Extra pod volumes. |
extraVolumeMounts |
[] |
Extra mounts for the main container. |
extraManifests |
[] |
Extra Kubernetes manifests rendered with the release. |
Validation
The chart is validated through HelmForge chart gates: strict linting, templating across CI values, helm-unittest, kubeconform with real CRD schemas, Artifact Hub linting, and k3d behavioral installs.