external-dns

Synchronizes Kubernetes Services, Ingresses and related sources with DNS providers. Apache-2.0. linux/amd64.

Pin this image by digest.

published Latest tag v0.23.0-tpio.1. Pin by digest. Usage is on this page.

Critical0
High0
Medium0
Low0
Gate passed
Open Usage

Pin by digest

oci.thepeoples.io/hardened/external-dns@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a

Pull:

podman pull oci.thepeoples.io/hardened/external-dns@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a

Verify:

cosign verify --key https://thepeoples.io/keys/oci.thepeoples.io.pub --insecure-ignore-tlog oci.thepeoples.io/hardened/external-dns@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a

Published 2026-10-20T13:33:54Z154.9 MiB compressed

Usage

Validated against this published digest: official-chart install, runtime digest, actual nonroot UID 65532, /healthz, Prometheus metrics, discovered source endpoints, CREATE of an annotated hostname, and registry/source gauge sync. Functional evidence.

external-dns runs as UID/GID 65532:65532 (the chart already uses those ids). The official chart constructs the image as {{ repository }}:{{ tag }} with no registry split; production values set repository: oci.thepeoples.io/hardened/external-dns. Chart policy is required (create-only, sync, or upsert-only); production sets upsert-only so records are never deleted. Keep replicaCount: 1 because the controller has no leader election. Chart default provider.name is aws. Production values set linode for this platform's clusters. Set LINODE_TOKEN (or the credentials for a different provider) and a unique txtOwnerId before install. Do not commit the token. Restrict domainFilters to the zones you manage.

The Service stays private (ClusterIP) on port 7979. external-dns needs Kubernetes API access to watch sources, so the values create RBAC and keep the service-account token mounted. Grant only the source permissions you enable. Annotate Services or Ingresses with external-dns.kubernetes.io/hostname (and target when the record should not follow the Service IP). The v0.22.0+ default prefix is external-dns.kubernetes.io/; the legacy external-dns.alpha.kubernetes.io/ prefix is ignored unless --enable-legacy-annotation-prefix is set.

Production Helm values (metrics enabled, image pinned by digest):

# Official external-dns chart 1.23.0. Replace both image placeholders
# with the tag/digest from the published catalog; a pending recipe is not a release.
# The chart image helper concatenates repository:tag with no registry split.
# Chart container UID/GID is already 65532. Chart policy is required.
fullnameOverride: external-dns
replicaCount: 1  # No leader election; more than one replica would double-write records.
nodeSelector: {kubernetes.io/arch: amd64, kubernetes.io/os: linux}
serviceAccount: {create: true, automountServiceAccountToken: true}
rbac: {create: true}
image:
  repository: oci.thepeoples.io/hardened/external-dns
  tag: "v0.23.0-tpio.1@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a"
  pullPolicy: IfNotPresent
sources:
  - service
  - ingress
# Chart default provider is aws. Override to your DNS host and set credentials
# before install. This platform's clusters use Linode (LINODE_TOKEN).
provider:
  name: linode
# Required: create-only, sync, or upsert-only. upsert-only never deletes records.
policy: upsert-only
registry: txt
# Set txtOwnerId to a unique id for this cluster before install.
txtOwnerId: cluster-1
interval: 1m
triggerLoopOnEvent: true
logLevel: info
logFormat: json
# Restrict managed zones. An empty filter considers every zone the provider returns.
domainFilters: []
securityContext:
  privileged: false
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 65532
  runAsGroup: 65532
  capabilities: {drop: [ALL]}
podSecurityContext:
  runAsNonRoot: true
  fsGroup: 65534
  seccompProfile: {type: RuntimeDefault}
resources:
  requests: {cpu: 50m, memory: 64Mi}
  limits: {cpu: "1", memory: 256Mi}
service: {enabled: true, port: 7979}
serviceMonitor: {enabled: false}
namespaced: false
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns
helm repo update external-dns
helm upgrade --install external-dns external-dns/external-dns --version 1.23.0 \
  --namespace dns --create-namespace -f values-hardened.yaml

The example Service is external-dns.dns.svc:7979. Metrics are on the same port. Forward the Service in a separate terminal:

kubectl -n dns port-forward service/external-dns 7979:7979

Then check liveness and Prometheus metrics:

curl -fsS http://127.0.0.1:7979/healthz
curl -fsS http://127.0.0.1:7979/metrics

/healthz or pod Ready alone is not a functional pass. A working controller creates or updates records for annotated sources and exposes source/registry gauges.

For existing Kustomize manifests that use the upstream image, pin this replacement in images:

images:
  - name: registry.k8s.io/external-dns/external-dns
    newName: oci.thepeoples.io/hardened/external-dns
    digest: sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a   # v0.23.0-tpio.1

Verify the latest published digest

cosign verify --key https://thepeoples.io/keys/oci.thepeoples.io.pub --insecure-ignore-tlog oci.thepeoples.io/hardened/external-dns@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a
cosign verify-attestation --key https://thepeoples.io/keys/oci.thepeoples.io.pub --insecure-ignore-tlog --type https://thepeoples.io/attestations/hardened-gate/v1 oci.thepeoples.io/hardened/external-dns@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a

The report views show the predicate types and commands for SPDX, scanner and optional OpenVEX attestations. Inspect the verified predicates as well as the signature.

Evidence

Publish and rescan reports for each tag. Gate is 0 CRITICAL / 0 HIGH from Trivy and Grype. MEDIUM/LOW are listed; they do not block publish. Remaining-CVE tables live here.

154.9 MiB · v0.23.0-tpio.1 · worst-of Trivy and Grype at publish.

Publication scan reports: Trivy · Grype · gate · Scan index

Corresponding source for v0.23.0-tpio.1 is at /oci/hardened/external-dns/v0.23.0-tpio.1/source/.

MEDIUM/LOW are best-effort and non-blocking. Remaining counts:

MEDIUM 0LOW 0

Fixed-version availability does not guarantee a compatible fix.

ScannerCVE / IDPackageInstalledSeverityFixed version available

Counts retain scanner gate semantics; the list deduplicates identical package/version records. Different scanner IDs can describe the same advisory.

Back to pull

This image is here as the DNS record synchronizer.

Why this image is connected
Why this image is connectedexternal-dnsDNS recordsynchronizer
  • external-dns: DNS record synchronizer

Status: published. Platform: linux/amd64.

Available tags and scan history

v0.23.0-tpio.1

Digest:

oci.thepeoples.io/hardened/external-dns@sha256:de0e5484b88efce284532ee015e03bfcc79952d687fbac7199d50b14738d6a1a

Published 2026-10-20T13:33:54Z UTC.

This tag used the first-publish cooldown break-glass (first publish; PhreeStyle GO 2026-10-19 12:26 PM CT).

Publication scan reports: Trivy / Grype / gate / OpenVEX

SPDX SBOM · OpenVEX: not present

Corresponding source available at /oci/hardened/external-dns/v0.23.0-tpio.1/source/, including exact upstream archives, SHA-256 sums, packaging, rebuild recipe and recorded changes.

Scan history for v0.23.0-tpio.1
Scanned (UTC)Kind / gateTrivyGrypeScanner DB versions/dates
2026-10-20T13:24:18Zpublish / passCRITICAL 0HIGH 0MEDIUM 0LOW 0CRITICAL 0HIGH 0MEDIUM 0LOW 0Trivy 0.75.0 / DB {"DownloadedAt": "2026-10-20T13:22:20.798411005Z", "NextUpdate": "2026-10-21T07:17:35.36859973Z", "UpdatedAt": "2026-10-20T07:17:35.368599961Z", "Version": 2} / 2026-10-20T07:17:35.368599961Z; Grype 0.120.1 / schema v6.1.10 / 2026-10-20T06:29:49Z

MEDIUM/LOW are best-effort and non-blocking. Remaining counts:

MEDIUM 0LOW 0

Fixed-version availability does not guarantee a compatible fix.

Remaining MEDIUM/LOW findings (0 distinct scanner/package/version records; 0 with fixed versions available)
ScannerCVE / IDPackageInstalledSeverityFixed version available

Counts retain scanner gate semantics; the list deduplicates identical package/version records. Different scanner IDs can describe the same advisory.

Rebuild and licence

The recipe rebuilds the binary from the pinned release with a digest-pinned Go toolchain, the standard golang.org/x/net@v0.60.0 bump, and a scratch runtime under UID 65532. Publication requires fresh unsuppressed 0 CRITICAL / 0 HIGH scans and functional validation for the exact digest.

Licence: Apache-2.0.

external-dns is Apache-2.0. Corresponding source is the official v0.23.0 tag tarball plus the Containerfile and recorded module changes. The archive license file is LICENSE.md.

Upstream source