diff --git a/docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml b/docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml
new file mode 100644
index 0000000000..b768cd2079
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml
@@ -0,0 +1,16 @@
+# ref: docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml
+---
+apiVersion: externaldns.k8s.io/v1alpha1
+kind: DNSEndpoint
+metadata:
+ name: cname-example
+ namespace: default
+ annotations:
+ dns.why/type: aws-localstack-tutorial
+spec:
+ endpoints:
+ - dnsName: www.example.com
+ recordTTL: 600
+ recordType: CNAME
+ targets:
+ - example.com
diff --git a/docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml b/docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml
new file mode 100644
index 0000000000..07c97180a5
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml
@@ -0,0 +1,28 @@
+# ref: docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml
+---
+apiVersion: externaldns.k8s.io/v1alpha1
+kind: DNSEndpoint
+metadata:
+ name: simple-example
+ namespace: default
+ annotations:
+ dns.why/type: aws-localstack-tutorial
+spec:
+ endpoints:
+ - dnsName: dnsendpoint-a.example.com
+ recordTTL: 300
+ recordType: A
+ targets:
+ - 192.168.1.100
+ - dnsName: dnsendpoint-a-lb.example.com
+ recordTTL: 200
+ recordType: A
+ targets:
+ - 10.0.1.1
+ - 10.0.1.2
+ - 10.0.1.3
+ - dnsName: dnsendpoint-aaaa.example.com
+ recordTTL: 600
+ recordType: AAAA
+ targets:
+ - 2001:0db8:85a3:0000:0000:8a2e:0370:7334
diff --git a/docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml b/docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml
new file mode 100644
index 0000000000..ec738aed71
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml
@@ -0,0 +1,19 @@
+# ref: docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml
+---
+apiVersion: externaldns.k8s.io/v1alpha1
+kind: DNSEndpoint
+metadata:
+ name: txt-example
+ namespace: default
+spec:
+ endpoints:
+ - dnsName: _acme-challenge.example.com
+ recordTTL: 300
+ recordType: TXT
+ targets:
+ - "validation-token-12345"
+ - dnsName: example.com
+ recordTTL: 3600
+ recordType: TXT
+ targets:
+ - "v=spf1 include:_spf.google.com ~all"
diff --git a/docs/snippets/tutorials/aws-localstack/fetch-records.sh b/docs/snippets/tutorials/aws-localstack/fetch-records.sh
new file mode 100755
index 0000000000..b128359220
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/fetch-records.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+set -e
+
+# run docs/snippets/tutorials/aws-localstack/check-records.sh
+
+export AWS_REGION=eu-west-1
+export AWS_ACCESS_KEY_ID=foo
+export AWS_SECRET_ACCESS_KEY=bar
+export AWS_ENDPOINT_URL=http://127.0.0.1:32379
+
+MATCH="${1:-}" # optional positional argument to filter records by name
+
+zones=$(aws route53 list-hosted-zones-by-name --query "HostedZones[].Id" --output json)
+
+echo "$zones" | jq -r '.[]' | while IFS= read -r hosted_zone_id; do
+ zone=${hosted_zone_id#"/hostedzone/"}
+ echo "Checking records for zone: $zone"
+
+ if [ -z "$MATCH" ]; then
+ # default behaviour (unchanged)
+ aws route53 list-resource-record-sets \
+ --hosted-zone-id "$zone" \
+ --query "ResourceRecordSets[].{Name:Name, Type:Type, Value:ResourceRecords[*].Value, TTL:TTL}" \
+ --output json
+ else
+ # filtered behaviour
+ aws route53 list-resource-record-sets \
+ --hosted-zone-id "$zone" \
+ --query "ResourceRecordSets[?contains(Name, \`${MATCH}\`)].{Name:Name, Type:Type, Value:ResourceRecords[*].Value, TTL:TTL}" \
+ --output json
+ fi
+done
diff --git a/docs/snippets/tutorials/aws-localstack/foo-app.yml b/docs/snippets/tutorials/aws-localstack/foo-app.yml
new file mode 100644
index 0000000000..b0ecde7787
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/foo-app.yml
@@ -0,0 +1,47 @@
+# ref: docs/snippets/tutorials/aws-localstack/foo-app.yml
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: foo-app
+ annotations:
+ external-dns.alpha.kubernetes.io/hostname: foo-app.example.com
+ dns.why/type: aws-localstack-tutorial
+spec:
+ type: ClusterIP
+ clusterIP: None
+ ports:
+ - port: 80
+ targetPort: 80
+ protocol: TCP
+ selector:
+ app: foo
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: foo-app
+ annotations:
+ dns.why/type: aws-localstack-tutorial
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: foo
+ template:
+ metadata:
+ labels:
+ app: foo
+ spec:
+ containers:
+ - name: foo
+ image: nginx:latest
+ ports:
+ - containerPort: 80
+ resources:
+ requests:
+ memory: "5Mi"
+ cpu: "25m"
+ limits:
+ memory: "5Mi"
+ cpu: "25m"
diff --git a/docs/snippets/tutorials/aws-localstack/kind.yaml b/docs/snippets/tutorials/aws-localstack/kind.yaml
new file mode 100644
index 0000000000..b959597f79
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/kind.yaml
@@ -0,0 +1,37 @@
+# ref: https://kind.sigs.k8s.io/docs/user/quick-start/
+# https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings
+
+# kind create cluster --config=docs/snippets/tutorials/aws-localstack/kind.yaml
+# kind delete cluster --name aws-localstack
+# kubectl cluster-info --context kind-aws-localstack
+# kubectl get nodes -o wide
+---
+kind: Cluster
+apiVersion: kind.x-k8s.io/v1alpha4
+name: aws-localstack
+networking:
+ apiServerAddress: 127.0.0.1
+ apiServerPort: 6443
+ ipFamily: dual
+nodes:
+- role: control-plane
+ kubeadmConfigPatches:
+ - |
+ kind: InitConfiguration
+ nodeRegistration:
+ kubeletExtraArgs:
+ node-labels: "ingress-ready=true"
+ extraPortMappings:
+ - containerPort: 80
+ hostPort: 8080
+ listenAddress: "0.0.0.0"
+ protocol: TCP
+ - containerPort: 43
+ hostPort: 4443
+ listenAddress: "0.0.0.0"
+ protocol: TCP
+ - containerPort: 32379 # inside kind node
+ hostPort: 32379 # exposed on host
+ listenAddress: "0.0.0.0"
+ protocol: TCP
+- role: worker
diff --git a/docs/snippets/tutorials/aws-localstack/service-lb.yml b/docs/snippets/tutorials/aws-localstack/service-lb.yml
new file mode 100644
index 0000000000..30a85f96d8
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/service-lb.yml
@@ -0,0 +1,18 @@
+# ref: docs/snippets/tutorials/aws-localstack/service-lb.yml
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: loadbalancer-service
+ annotations:
+ external-dns.alpha.kubernetes.io/hostname: my-loadbalancer.example.com
+ dns.why/type: aws-localstack-tutorial
+ namespace: default
+spec:
+ type: LoadBalancer
+ ports:
+ - port: 80
+ name: http
+ targetPort: 80
+ selector:
+ app: test-app
diff --git a/docs/snippets/tutorials/aws-localstack/values-extdns.yml b/docs/snippets/tutorials/aws-localstack/values-extdns.yml
new file mode 100644
index 0000000000..8e31ec2a3b
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/values-extdns.yml
@@ -0,0 +1,29 @@
+# ref: https://github.com/kubernetes-sigs/external-dns/blob/master/charts/external-dns/values.yaml
+logLevel: info # debug, info, warn, error
+policy: sync
+
+provider:
+ name: aws
+
+txtOwnerId: aws-localstack
+
+domainFilters:
+ - example.com
+ - local.tld
+
+sources:
+ - service
+ - ingress
+ - crd
+
+env:
+ - name: AWS_REGION
+ value: eu-west-1
+ - name: AWS_ACCESS_KEY_ID
+ value: foo-fake
+ - name: AWS_SECRET_ACCESS_KEY
+ value: bar-fake
+ - name: AWS_ENDPOINT_URL
+ value: http://localstack.localstack.svc.cluster.local:4566
+
+interval: 1m
diff --git a/docs/snippets/tutorials/aws-localstack/values-localstack.yml b/docs/snippets/tutorials/aws-localstack/values-localstack.yml
new file mode 100644
index 0000000000..ec689102d0
--- /dev/null
+++ b/docs/snippets/tutorials/aws-localstack/values-localstack.yml
@@ -0,0 +1,50 @@
+# ref: https://github.com/localstack/helm-charts/blob/main/charts/localstack/values.yaml
+
+debug: false
+
+extraLabels:
+ app: localstack
+
+extraEnvVars:
+ - name: SERVICES
+ value: "route53"
+
+# -- Set a fixed port for LocalStack edge service --
+service:
+ type: NodePort
+ edgeService:
+ name: edge
+ targetPort: 4566
+ nodePort: 32379
+
+enableStartupScripts: true
+startupScriptContent: |
+ #!/bin/bash
+ create_zone_if_missing() {
+ ZONE_NAME="$1"
+ COMMENT="$2"
+
+ EXISTING_ZONE_ID=$(
+ awslocal route53 list-hosted-zones-by-name \
+ --dns-name "${ZONE_NAME}." \
+ --query "HostedZones[?Name=='${ZONE_NAME}.'].Id | [0]" \
+ --output text
+ )
+
+ if [ "$EXISTING_ZONE_ID" != "None" ]; then
+ echo "Route53 zone '${ZONE_NAME}' already exists (${EXISTING_ZONE_ID})"
+ return 0
+ fi
+
+ echo "Creating Route53 zone '${ZONE_NAME}'"
+ awslocal route53 create-hosted-zone \
+ --name "$ZONE_NAME" \
+ --caller-reference "$(date +%s)" \
+ --hosted-zone-config Comment="$COMMENT"
+ }
+
+ create_zone_if_missing "local.tld" "external-dns"
+ create_zone_if_missing "example.com" "external-dns"
+
+lambda:
+ executor: "kubernetes"
diff --git a/docs/tutorials/aws-localstack.md b/docs/tutorials/aws-localstack.md
new file mode 100644
index 0000000000..6ef5b475d5
--- /dev/null
+++ b/docs/tutorials/aws-localstack.md
@@ -0,0 +1,484 @@
+---
+tags: ["tutorial", "kind", "localstack", "aws"]
+---
+
+# AWS and LocalStack
+
+## Overview
+
+This tutorial demonstrates how to configure ExternalDNS to manage DNS records in LocalStack's Route53 service using a local Kind (Kubernetes in Docker) cluster.
+
+### TL;DR
+
+After completing this lab, you will have a Kubernetes environment running as containers in your local development machine with localstack and external-dns.
+
+## Prerequisite
+
+Before you start, ensure you have:
+
+- A running kubernetes cluster.
+ - In this tutorial we are going to use [kind](https://kind.sigs.k8s.io/)
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) and [`helm`](https://helm.sh/)
+- `external-dns` source code or [helm chart](https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns)
+- `Localstack` how to [documenation](https://docs.localstack.cloud/)
+- Optional
+ - `AWS` [cli](https://aws.amazon.com/cli/)
+ - `Localstack` [cli](https://docs.localstack.cloud/aws/getting-started/installation/)
+
+## Architecture Overview
+
+In this setup:
+
+- `Kind` provides a local `Kubernetes` cluster
+- `LocalStack` simulates AWS services (specifically `Route53`)
+- `ExternalDNS` automatically creates DNS records in `LocalStack` based on `Kubernetes` resources
+
+## Bootstrap Environment
+
+### 1. Create cluster
+
+```sh
+kind create cluster --config=docs/snippets/tutorials/aws-localstack/kind.yaml
+
+Creating cluster "aws-localstack" ...
+ â Preparing nodes đĻ đĻ
+ â Writing configuration đ
+ â Starting control-plane đšī¸
+ â Installing CNI đ
+ â Installing StorageClass đž
+ â Joining worker nodes đ
+Set kubectl context to "kind-aws-localstack"
+You can now use your cluster with:
+
+kubectl cluster-info --context kind-aws-localstack
+```
+
+Verify the cluster is running:
+
+```bash
+kubectl cluster-info --context kind-aws-localstack
+kubectl get nodes
+```
+
+### 2. Deploy Localstack
+
+There are multiple options to configure etcd
+
+1. With custom manifest.
+2. Localstack [helm](https://docs.localstack.cloud/aws/integrations/containers/kubernetes/)
+
+In this tutorial, we'll use the second option.
+
+```sh
+helm repo add localstack https://localstack.github.io/helm-charts
+helm upgrade localstack localstack-charts/localstack \
+ -n localstack \
+ --create-namespace \
+ --install \
+ --atomic \
+ --wait \
+ -f docs/snippets/tutorials/aws-localstack/values-localstack.yml
+
+â¯â¯ Release "localstack" does not exist. Installing it now.
+```
+
+Verify LocalStack is running
+
+```sh
+kubectl get pods -n localstack
+kubectl logs deploy/localstack -n localstack
+```
+
+### 3: Create a Hosted Zone in LocalStack
+
+Test if we could reach `Localstack`, `Route53` service is available and verify `Route53` zones created in localstack.
+
+```sh
+curl http://127.0.0.1:$NODE_PORT/_localstack/health | jq
+docs/snippets/tutorials/aws-localstack/fetch-records.sh
+```
+
+Create extra hosted zones in localstack when required
+
+```sh
+export AWS_ACCESS_KEY_ID=test
+export AWS_SECRET_ACCESS_KEY=test
+export AWS_DEFAULT_REGION=us-east-1
+export AWS_ENDPOINT_URL=http://127.0.0.1:32379
+
+aws route53 create-hosted-zone \
+ --name test.com \
+ --caller-reference $(date +%s)
+```
+
+### 4. Configure ExternalDNS
+
+Deploy with helm and minimal configuration.
+
+Add the `external-dns` helm repository and check available versions
+
+```sh
+helm repo add --force-update external-dns https://kubernetes-sigs.github.io/external-dns/
+helm search repo external-dns --versions
+```
+
+Install with required configuration
+
+```sh
+helm upgrade --install external-dns external-dns/external-dns \
+ -n default \
+ -f docs/snippets/tutorials/aws-localstack/values-extdns.yml
+
+â¯â¯ Release "external-dns" does not exist. Installing it now.
+```
+
+Validate pod status and view logs
+
+```sh
+kubectl get pods -l app.kubernetes.io/name=external-dns
+kubectl logs deploy/external-dns -n default
+```
+
+Or run it on the host from sources
+
+```sh
+# required to access localstack
+export AWS_REGION=eu-west-1
+export AWS_ACCESS_KEY_ID=foo
+export AWS_SECRET_ACCESS_KEY=bar
+export AWS_ENDPOINT_URL=http://127.0.0.1:32379
+
+go run main.go \
+ --provider=aws \
+ --source=service \
+ --source=ingress \
+ --source=crd \
+ --txt-owner-id=aws-localstack \
+ --domain-filter=example.com \
+ --domain-filter=local.tld \
+ --log-level=info
+```
+
+## 5. Test with a Sample Service
+
+Create a test service so that ExternalDNS to create records
+
+```yaml
+[[% include 'tutorials/aws-localstack/foo-app.yml' %]]
+```
+
+Deploy the service
+
+```sh
+kubectl apply -f docs/snippets/tutorials/aws-localstack/foo-app.yml
+```
+
+Validate `route53` records created
+
+```sh
+docs/snippets/tutorials/aws-localstack/fetch-records.sh "foo-app"
+
+â¯â¯ [
+ {
+ "Name": "a-foo-app.example.com.",
+ "Type": "TXT",
+ },
+ {
+ "Name": "foo-app.example.com.",
+ "Type": "A",
+ "Value": [
+ "10.244.1.18",
+ ],
+ "TTL": 300
+ }
+]
+```
+
+## 6. Using DNSEndpoint CRD (Advanced)
+
+The DNSEndpoint Custom Resource Definition (CRD) provides direct control over DNS records, independent of Services or Ingresses. This is useful for:
+
+- Creating DNS records that don't correspond to Kubernetes services
+- Managing complex DNS configurations (multiple targets, weighted routing)
+- Integrating with external systems or custom controllers
+
+Verify the CRD is installed
+
+```sh
+kubectl get crd dnsendpoints.externaldns.k8s.io
+```
+
+### Example 1: Multiple Records
+
+Create a simple A record pointing to a specific IP
+
+```yaml
+[[% include 'tutorials/aws-localstack/dnsendpoint-multi.yml' %]]
+```
+
+Apply and verify
+
+```sh
+kubectl apply -f docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml
+# Check the DNSEndpoint status
+kubectl get dnsendpoint simple-example -o yaml
+# validate
+docs/snippets/tutorials/aws-localstack/fetch-records.sh "dnsendpoint-a"
+docs/snippets/tutorials/aws-localstack/fetch-records.sh "dnsendpoint-aaaa"
+
+â¯â¯ [
+ {
+ "Name": "a-dnsendpoint-a.example.com.",
+ "Type": "TXT",
+ "Value": [
+ "heritage=external-dns,external-dns/owner=aws-localstack"
+ ],
+ "TTL": 300
+ },
+ {
+ "Name": "dnsendpoint-a.example.com.",
+ "Type": "A",
+ "Value": [
+ "192.168.1.100"
+ ],
+ "TTL": 300
+ },
+ {
+ "Name": "dnsendpoint-aaaa.example.com.",
+ "Type": "AAAA",
+ "Value": [
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
+ ],
+ "TTL": 600
+ },
+]
+```
+
+### Example 2: CNAME Record
+
+Create a CNAME record pointing to another domain:
+
+```yaml
+[[% include 'tutorials/aws-localstack/dnsendpoint-cname.yml' %]]
+```
+
+Apply and verify
+
+```sh
+kubectl apply -f docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml
+# Check the DNSEndpoint status
+kubectl get dnsendpoint cname-example -o yaml
+# validate
+docs/snippets/tutorials/aws-localstack/fetch-records.sh "www.example"
+
+â¯â¯ [
+ {
+ "Name": "a-www.example.com.",
+ "Type": "TXT",
+ "Value": [
+ "\"heritage=external-dns,external-dns/owner=aws-localstack,external-dns/resource=crd/default/cname-example\""
+ ],
+ "TTL": 300
+ },
+ {
+ "Name": "www.example.com.",
+ "Type": "A",
+ "Value": [
+ "example.com"
+ ],
+ "TTL": 600
+ }
+]
+```
+
+### Example 4: TXT Records
+
+Create TXT records (useful for domain verification, SPF, DKIM, etc.)
+
+```yaml
+[[% include 'tutorials/aws-localstack/dnsendpoint-txt.yml' %]]
+```
+
+Apply and verify
+
+```sh
+kubectl apply -f docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml
+# Check the DNSEndpoint status
+kubectl get dnsendpoint txt-example -o yaml
+# validate
+docs/snippets/tutorials/aws-localstack/fetch-records.sh
+```
+
+## 7. Test with Service LoadBalancer (Advanced)
+
+With Kind, LoadBalancer services won't get external IPs automatically. You can:
+
+- Use [MetalLB](https://metallb.io/) for LoadBalancer support in Kind
+- Install and run [Cloud Provider KInd](https://kind.sigs.k8s.io/docs/user/loadbalancer/)
+- Patch services, to manually assign an Ingress IPs. It just makes the Service appear like a real LoadBalancer for tools/tests.
+
+```yaml
+[[% include 'tutorials/aws-localstack/service-lb.yml' %]]
+```
+
+Apply, patch and verify
+
+```sh
+kubectl apply -f docs/snippets/tutorials/aws-localstack/service-lb.yml
+# patch
+kubectl patch svc loadbalancer-service --type=merge \
+ -p '{"status":{"loadBalancer":{"ingress":[{"ip":"172.18.0.2"}]}}}' --subresource=status
+â¯â¯ service/loadbalancer-service
+# validate
+docs/snippets/tutorials/aws-localstack/fetch-records.sh "my-loadbalancer"
+```
+
+### Cleanup
+
+Remove all resources:
+
+```sh
+kind delete cluster --name aws-localstack
+```
+
+## Diagrams
+
+### System Architecture
+
+**Description:** This diagram illustrates the complete setup where ExternalDNS runs inside the Kind cluster, watches Kubernetes Service and Ingress resources, and automatically creates corresponding DNS records in LocalStack's Route53 service.
+Both the Kind cluster and LocalStack container run on the same Docker network, enabling communication between them.
+
+```mermaid
+graph TB
+ subgraph "Host Machine"
+ kubectl[kubectl CLI]
+ awscli[AWS CLI]
+ end
+
+ subgraph "Docker Network: kind"
+ subgraph "Kind Cluster"
+ subgraph "Control Plane"
+ api[API Server]
+ end
+
+ subgraph "Namespace: external-dns"
+ ed[ExternalDNS Pod]
+ end
+
+ subgraph "Namespace: default"
+ nginx[Nginx Pod]
+ svc[Service
nginx.example.com]
+ ing[Ingress
nginx-ingress.example.com]
+ end
+ end
+
+ ls[LocalStack Container
Route53 Mock]
+ end
+
+ kubectl -->|manages| api
+ awscli -->|configures DNS| ls
+ ed -->|watches| svc
+ ed -->|watches| ing
+ ed -->|creates/updates
DNS records| ls
+ api -->|provides resources| ed
+ svc -->|routes to| nginx
+ ing -->|routes to| svc
+
+ style ed fill:#326ce5,color:#fff
+ style ls fill:#ff9900,color:#fff
+ style kubectl fill:#326ce5,color:#fff
+ style awscli fill:#ff9900,color:#fff
+```
+
+### DNS Record Creation Flow
+
+**Description:** This sequence diagram demonstrates the automated DNS lifecycle management. When you create a Service with an ExternalDNS annotation, ExternalDNS detects the new resource, extracts the hostname, and creates corresponding DNS records in LocalStack.
+It also creates TXT records for ownership tracking. When the Service is deleted, ExternalDNS automatically cleans up the DNS records.
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant K8s as Kubernetes API
+ participant ED as ExternalDNS
+ participant LS as LocalStack Route53
+
+ User->>K8s: kubectl apply -f service.yaml
+ K8s->>K8s: Service created
+
+ Note over ED: Watches for Service changes
+ K8s->>ED: Service event detected
+ ED->>ED: Parse annotation:
nginx.example.com
+ ED->>LS: Check existing records
+ LS-->>ED: No record exists
+ ED->>LS: Create A record
nginx.example.com â LoadBalancer IP
+ LS->>LS: Record created
+ LS-->>ED: Success
+ ED->>LS: Create TXT record
"heritage=external-dns,..."
+ LS-->>ED: Success
+
+ Note over ED: Continues watching for changes
+
+ User->>K8s: kubectl delete service nginx
+ K8s->>ED: Service deletion event
+ ED->>LS: Delete A record
+ ED->>LS: Delete TXT record
+ LS-->>ED: Records deleted
+```
+
+### ExternalDNS Decision Flow
+
+**Description:** This flowchart illustrates ExternalDNS's decision-making process.
+It checks for DNS annotations, validates the domain filter, ensures IP addresses are available, and uses TXT records to track ownership.
+This prevents conflicts when multiple DNS controllers or manual DNS entries exist.
+The ownership mechanism ensures ExternalDNS only modifies records it created.
+
+```mermaid
+flowchart TD
+ Start([ExternalDNS detects
Kubernetes resource])
+
+ Start --> CheckAnnotation{Has external-dns
annotation?}
+ CheckAnnotation -->|No| Skip[Skip - No DNS needed]
+ CheckAnnotation -->|Yes| ExtractHost[Extract hostname from
annotation]
+
+ ExtractHost --> CheckDomain{Hostname matches
domain-filter?}
+ CheckDomain -->|No| Skip2[Skip - Outside managed domain]
+ CheckDomain -->|Yes| GetIP[Get LoadBalancer IP or
Ingress address]
+
+ GetIP --> CheckIP{IP/Address
available?}
+ CheckIP -->|No| Wait[Wait for IP assignment]
+ CheckIP -->|Yes| QueryRoute53[Query LocalStack Route53
for existing record]
+
+ QueryRoute53 --> CheckExists{Record
exists?}
+ CheckExists -->|No| Create[Create new A record
+ TXT ownership record]
+ CheckExists -->|Yes| CheckOwner{Check TXT record
owner ID}
+
+ CheckOwner -->|Not owned by us| Skip3[Skip - Managed by
another controller]
+ CheckOwner -->|Owned by us| CheckIP2{IP changed?}
+
+ CheckIP2 -->|No| NoAction[No action needed]
+ CheckIP2 -->|Yes| Update[Update A record
with new IP]
+
+ Create --> Success([DNS record created])
+ Update --> Success
+ NoAction --> Success
+ Skip --> End([End])
+ Skip2 --> End
+ Skip3 --> End
+ Wait --> End
+ Success --> End
+
+ style Start fill:#90EE90,color:#000
+ style Success fill:#90EE90,color:#000
+ style Create fill:#ADD8E6,color:#000
+ style Update fill:#ADD8E6,color:#000
+ style Skip fill:#FFB6C1,color:#000
+ style Skip2 fill:#FFB6C1,color:#000
+ style Skip3 fill:#FFB6C1,color:#000
+```
+
+## Additional Resources
+
+- [ExternalDNS Documentation](https://github.com/kubernetes-sigs/external-dns)
+- [Kind Documentation](https://kind.sigs.k8s.io/)
+- [LocalStack Documentation](https://docs.localstack.cloud/)
+- [AWS Route53 API Reference](https://docs.aws.amazon.com/route53/)
diff --git a/docs/tutorials/coredns-etcd.md b/docs/tutorials/coredns-etcd.md
index 8f8ee0db99..be58d9d42a 100644
--- a/docs/tutorials/coredns-etcd.md
+++ b/docs/tutorials/coredns-etcd.md
@@ -1,3 +1,7 @@
+---
+tags: ["tutorial", "coredns", "etcd", "kind"]
+---
+
# CoreDNS with etcd backend
## Overview
@@ -27,7 +31,7 @@ Before you start, ensure you have:
- `CoreDNS` [helm chart](https://github.com/coredns/helm)
- Optional
- `dnstools` container for testing
- - `etcdctl` to interat with [etcd](https://etcd.io/docs/v3.4/dev-guide/interacting_v3/)
+ - `etcdctl` to interact with [etcd](https://etcd.io/docs/v3.4/dev-guide/interacting_v3/)
## Bootstrap Environment
diff --git a/mkdocs.yml b/mkdocs.yml
index c25db22bed..9777e2cca5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -58,6 +58,7 @@ theme:
- navigation.instant
- navigation.tabs
- navigation.tabs.sticky
+ - tags
extra:
version: