diff --git a/EXTERNAL_BIND9.md b/EXTERNAL_BIND9.md
new file mode 100644
index 00000000..f9d3716a
--- /dev/null
+++ b/EXTERNAL_BIND9.md
@@ -0,0 +1,449 @@
+# External BIND9 Configuration
+
+This document describes how to configure the designate-operator to use
+pre-existing BIND9 servers that are **not** managed by the operator. This is
+useful when you already have authoritative DNS infrastructure and want
+Designate to push zone data to those servers via RNDC.
+
+## Overview
+
+The operator-managed BIND9 backends (`DesignateBackendbind9` resources) run
+inside the Kubernetes cluster as StatefulSets. External BIND9 support lets you
+add one or more BIND9 servers that live outside the cluster (or are managed by
+a different system) as additional pool targets. Designate's worker service
+connects to them over RNDC to update zones and sends DNS NOTIFY so the external
+servers can pull zone data via AXFR from the mDNS service.
+
+External BIND9 targets are **appended** to the default pool alongside any
+operator-managed backends. You can run a mix of internal and external targets
+in the same pool, or external-only targets when `bind9ReplicaCount` is zero.
+
+> **Multipool limitation:** External binds are fully supported in single-pool
+> (default pool) deployments only. If `designate-multipool-config` is present,
+> the operator still parses the secret and generates RNDC keys, but external
+> targets are **not** added to `pools.yaml` today.
+
+## Architecture
+
+```mermaid
+flowchart LR
+ subgraph User
+ S["Secret
(keys = pool names)"]
+ CR["Designate CR
externalBindsSecret"]
+ end
+
+ subgraph Operator
+ R[ReadExternalBinds]
+ RS["Secret designate-external-rndc"]
+ PY["ConfigMap designate-pools-yaml-config-map"]
+ PU[Pool update job]
+ end
+
+ subgraph Pods
+ W[designate-worker]
+ M[designate-mdns]
+ LB["LoadBalancer Service
(external master)"]
+ end
+
+ subgraph External
+ B[External BIND9]
+ end
+
+ S --> CR --> R
+ R --> RS --> W
+ R --> PY --> PU
+ W -->|"RNDC (953)"| B
+ W -->|"NOTIFY (53)"| B
+ B -->|"AXFR (5354)"| M
+ LB -.->|"ingress IP in pools.yaml"| PY
+ B -->|"AXFR (5354)"| LB
+```
+
+### Traffic paths
+
+| Path | Port | Direction | Required for |
+|------|------|-----------|--------------|
+| Worker → external BIND | RNDC (953 default) | Outbound from worker pods | Zone updates via RNDC |
+| Worker → external BIND | DNS (53 default) | Outbound from worker pods | NOTIFY to trigger AXFR |
+| External BIND → mDNS / external master | mDNS (5354) | Inbound to master IPs | AXFR zone transfer |
+
+The worker is the source of both RNDC calls and DNS NOTIFY messages to external
+BIND9 targets. mDNS pods must be reachable from external BIND9 servers on port
+5354 so that BIND9 can perform the subsequent AXFR zone transfer.
+
+## Prerequisites
+
+### Designate configuration
+
+External targets appear in `pools.yaml` only when **all** of the following are
+true:
+
+1. `spec.externalBindsSecret` references a valid secret in the same namespace
+ as the Designate CR.
+2. NS records are configured (`spec.nsRecords` and/or the
+ `designate-ns-records-params` ConfigMap).
+3. `DesignateCentral` is ready (`designateCentralReadyCount > 0`).
+
+Without NS records, the operator does not generate or update the pools
+ConfigMap, so external binds have no effect on the running pool.
+
+### External BIND9 server configuration
+
+For each external server:
+
+- RNDC must be enabled and reachable from designate-worker pods on the RNDC
+ port (default 953).
+- The server must accept NOTIFY from the workers IPs and allow AXFR
+ from those masters on the specified port.
+- `rndckeyname` and `rndcalgorithm` in the secret must match the key clause in
+ the server's `named.conf` / `rndc.conf`.
+- The RNDC shared secret must be base64-encoded in the secret (same encoding
+ BIND uses in its config files).
+
+### Networking on OpenShift / Kubernetes
+
+Pod traffic is often not routable to external networks by default. Additional cluster
+configuration may be necessary to configure the necessary connectivity between the
+external DNS server, designate-worker pods and the kubernetes services for the
+mDNS pods.
+
+1. **Create a NetworkAttachmentDefinition (NAD)** in the **same namespace** as
+ the Designate pods. The NAD must provide connectivity to the network where
+ the external BIND9 servers reside.
+
+2. **Attach the NAD to designate-worker** via the Designate
+ CR to worker for RNDC and NOTIFY:
+
+ ```yaml
+ apiVersion: designate.openstack.org/v1beta1
+ kind: Designate
+ metadata:
+ name: designate
+ namespace: openstack
+ spec:
+ designateWorker:
+ networkAttachments:
+ - external-dns-net # NAD name
+ - designate
+ # ...
+ ```
+
+3. **Verify routing** on the additional interface. Depending on topology you
+ may need static routes or a gateway configured in the NAD.
+
+
+## Step 1: Create the External Binds Secret
+
+Create a Kubernetes Secret in the **same namespace** as the Designate CR. Each
+secret data **key** is a pool name; each **value** is a YAML array of external
+BIND9 entries.
+
+In single-pool mode the key must be `default`:
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: designate-external-binds
+ namespace: openstack
+type: Opaque
+stringData:
+ default: |
+ - name: site-a-ns1
+ address: 192.168.100.10
+ rndcsecret: c2VjcmV0YmFzZTY0ZW5jb2RlZA==
+ - name: site-a-ns2
+ address: 192.168.100.11
+ rndcsecret: YW5vdGhlcnNlY3JldA==
+ rndckeyname: my-rndc-key
+ rndcalgorithm: hmac-sha512
+ rndchost: 192.168.100.12
+ rndcport: 5953
+ port: 5353
+```
+
+### Field reference
+
+| Field | Required | Default | Description |
+|-------|----------|---------|-------------|
+| `name` | No | `external-bind9-{address}` | Human-readable name used in pool target descriptions. |
+| `address` | **Yes** | — | IP address of the BIND9 server (used for DNS and as the nameserver entry). |
+| `port` | No | `53` | DNS port on the BIND9 server. |
+| `rndcsecret` | **Yes** | — | Base64-encoded RNDC shared secret. |
+| `rndckeyname` | No | `rndc-key` | Name of the RNDC key clause. Must match the key name in the BIND9 server's `rndc.conf` / `named.conf`. |
+| `rndcalgorithm` | No | `hmac-sha256` | Algorithm for RNDC. Common values: `hmac-sha256`, `hmac-sha512`. |
+| `rndchost` | No | value of `address` | Host for RNDC connections (use when RNDC listens on a different address than DNS). |
+| `rndcport` | No | `953` | Port for RNDC connections. |
+
+The secret may contain keys for multiple pools (for example `default` and
+`my-other-pool`). Only the `default` key is supported at the time of writing.
+
+## Step 2: Reference the Secret in the Designate CR
+
+Add `externalBindsSecret` to your Designate custom resource. The value is the
+secret **name** only.
+
+```yaml
+apiVersion: designate.openstack.org/v1beta1
+kind: Designate
+metadata:
+ name: designate
+ namespace: openstack
+spec:
+ externalBindsSecret: designate-external-binds
+ nsRecords:
+ - hostname: ns1.example.com.
+ priority: 1
+ # ... rest of your spec
+```
+
+## What Happens
+
+When `externalBindsSecret` is set, the operator:
+
+1. **Reads and validates** the secret. Each entry must have `address` and
+ `rndcsecret`. Validation errors set `InputReady=False` on the Designate CR.
+ A missing secret sets `InputReady=False` and requeues after 10 seconds.
+
+2. **Generates RNDC key files** in a derived secret named `designate-external-rndc`
+ (fixed name, owned by the Designate CR). Each external entry becomes one
+ secret key `{poolName}-rndc-{index}` (for example `default-rndc-0`) with
+ content like:
+ ```
+ key "rndc-key" {
+ algorithm hmac-sha256;
+ secret "c2VjcmV0YmFzZTY0ZW5jb2RlZA==";
+ };
+ ```
+
+3. **Mounts RNDC keys into workers.** The operator merges `designate-bind-secret`
+ (internal BIND keys) and `designate-external-rndc` via a projected volume at
+ `/etc/designate/rndc-keys` in designate-worker pods. External keys are
+ mounted at `/etc/designate/rndc-keys/{poolName}-rndc-{index}`.
+
+4. **Adds external targets to pools.yaml.** Each entry becomes a bind9 target
+ and nameserver appended after internal targets. External targets use per-entry
+ RNDC credentials. External nameserver addresses are also appended to
+ the pool's `nameservers` list.
+
+5. **Tracks changes.** A hash of the referenced secret content is stored in
+ `status.hash["designate-external-binds"]`. When the secret changes, the
+ operator regenerates `designate-external-rndc` and `pools.yaml`, rolls out
+ worker pods (deployment hash env change), and may launch a **pool update job**
+ when the pools hash changes.
+
+The operator watches both the external binds secret and labeled external-master
+LoadBalancer Services, so changes to either trigger reconciliation automatically.
+
+## Master IP selection
+
+Internal and external targets use different master addresses in `pools.yaml`:
+
+```mermaid
+flowchart TD
+ A[External BIND target] --> B{External master LB Services
with ingress IP for this pool?}
+ B -->|Yes| C["masters = LB ingress IPs
(port 5354)"]
+ B -->|No| D["masters = internal mDNS
predictable IPs (port 5354)"]
+ C --> E[External targets only]
+ D --> E
+ F[Internal BIND targets] --> G["masters = internal mDNS IPs always"]
+```
+
+When no external master LoadBalancer is configured, external BIND servers must be
+able to reach internal mDNS predictable IPs on port 5354 (often requiring the
+same Multus attachments described above).
+
+## Removing External BIND9
+
+To remove external BIND9 backends:
+
+1. Remove `externalBindsSecret` from the Designate CR (or set it to an empty
+ string).
+2. The operator clears `designate-external-rndc` data, clears
+ `status.hash["designate-external-binds"]`, and regenerates `pools.yaml`
+ without external targets.
+3. Delete the user-managed external binds secret when no longer needed.
+
+## External Master Services
+
+By default, external BIND9 targets receive NOTIFY from worker pod addresses and
+perform AXFR on internal mDNS pod addresses. When pod-to-external routing is
+difficult, expose mDNS on a routable LoadBalancer IP. The operator reads the
+LoadBalancer **ingress IP** and writes it into `pools.yaml` as the master for
+**external targets only**, so that external BIND9 servers can reach mDNS for
+AXFR zone transfers.
+
+Creating the Service makes the IP available to Designate configuration; you
+must still configure `spec.selector`, firewall rules, and external BIND
+`allow-transfer` / `also-notify` for real zone-transfer traffic.
+
+### Service metadata vs pod selector
+
+The external master Service uses **two different label sets**:
+
+```
+External Master LoadBalancer Service
+├── metadata.labels ← operator discovery (fixed values)
+│ designate.openstack.org/external_master: "true"
+│ service: "designate-mdns"
+│
+└── spec.selector ← Kubernetes endpoint selection (match mDNS pods)
+ service: "designate-mdns"
+```
+
+Always include a selector such as `service: designate-mdns` to make sure the
+Service matches to a designate-mdns pod. You can create multiple services with
+different selector criteria such as a pod-name selector, e.g.
+`statefulset.kubernetes.io/pod-name: designate-mdns-0` and create a service for
+each mdns replica.
+
+### Creating an External Master Service
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: designate-mdns-external
+ namespace: openstack
+ labels:
+ designate.openstack.org/external_master: "true"
+ service: designate-mdns
+ annotations:
+ # Optional: restrict to a specific pool (omit to apply to all pools)
+ designate.openstack.org/external_pool: "default"
+spec:
+ type: LoadBalancer
+ ports:
+ - name: mdns
+ port: 5354
+ targetPort: 5354
+ protocol: TCP
+ selector:
+ service: designate-mdns # use -mdns if CR is not named "designate"
+```
+
+### How master discovery works
+
+The operator lists Services in the Designate namespace matching **both**
+metadata labels:
+
+- `designate.openstack.org/external_master: "true"`
+- `service: designate-mdns`
+
+It then filters by:
+
+- **Service type:** only `LoadBalancer` services are considered.
+- **Pool annotation:** if `designate.openstack.org/external_pool` is present,
+ the service applies only to the named pool (must match the secret data key,
+ for example `default`). If absent, the service applies to all pools.
+- **Ingress IP:** the service must have `status.loadBalancer.ingress[0].ip`
+ assigned. Services without an IP will force a reconcile until the service
+ receives an IP. Hostname-only ingress entries are not supported as the IPs are
+ required for designate pool configuration. Each matching service contributes
+ at most one IP; multiple matching services produce multiple master entries
+ (sorted).
+
+When external master IPs exist for a pool, they replace internal mDNS addresses
+as masters for that pool's **external** targets only. Internal targets always
+use internal mDNS predictable IPs.
+
+## Example: Minimal Setup
+
+This example adds one external BIND9 server to the default pool.
+
+**1. Create the secret:**
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: my-external-bind9
+ namespace: openstack
+type: Opaque
+stringData:
+ default: |
+ - name: campus-ns1
+ address: 198.51.100.53
+ rndcsecret: bXlybmRjc2VjcmV0 # base64-encoded RNDC secret
+```
+
+**2. Reference it in the Designate CR (NS records required):**
+
+```yaml
+apiVersion: designate.openstack.org/v1beta1
+kind: Designate
+metadata:
+ name: designate
+ namespace: openstack
+spec:
+ externalBindsSecret: my-external-bind9
+ nsRecords:
+ - hostname: ns1.example.com.
+ priority: 1
+ # ... other spec fields
+```
+
+**3. (Optional) Expose mDNS for external access:**
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: designate-mdns-external
+ namespace: openstack
+ labels:
+ designate.openstack.org/external_master: "true"
+ service: designate-mdns
+spec:
+ type: LoadBalancer
+ ports:
+ - name: mdns
+ port: 5354
+ targetPort: 5354
+ selector:
+ service: designate-mdns
+ component: designate-mdns
+```
+
+## Troubleshooting
+
+**Designate reports `InputReady=False`:**
+
+- Confirm the secret named in `externalBindsSecret` exists in the **same
+ namespace** as the Designate CR.
+- Verify every entry has both `address` and `rndcsecret`.
+- Ensure the YAML is well-formed (a list of maps under each pool key).
+- In single-pool mode, use the `default` key in the secret data.
+
+**External targets missing from pools.yaml:**
+
+- Confirm NS records are configured and DesignateCentral is ready.
+- If using multipool mode (`designate-multipool-config` present), external binds
+ are not yet merged into generated pools.
+
+**External zones are not updating:**
+
+- Confirm the external BIND server is reachable from worker pods on the RNDC
+ port (default 953).
+- Verify `rndckeyname` and `rndcalgorithm` match the BIND9 RNDC configuration.
+- Check that BIND allows zone transfers from the configured master IPs (internal
+ mDNS predictable IPs or external master LoadBalancer ingress IP).
+- Confirm worker pods can send NOTIFY to the external server on port 53 and
+ that the external server can AXFR from mDNS on port 5354.
+
+**External master service is ignored:**
+
+- Service type must be `LoadBalancer` with `status.loadBalancer.ingress[0].ip`
+ assigned (not hostname-only).
+- Metadata labels must include `designate.openstack.org/external_master: "true"`
+ and `service: designate-mdns`.
+- If `designate.openstack.org/external_pool` is set, it must currently be
+ `default`.
+- For actual zone transfers, verify `spec.selector` matches mDNS pod labels and
+ that firewalls allow AXFR on port 5354 to the LoadBalancer IP.
+
+**Worker rolled out but pool still stale:**
+
+- A pools hash change triggers a pool update job in addition to the worker
+ rollout. Check pool update job logs if external targets appear in the
+ `designate-pools-yaml-config-map` ConfigMap but Designate has not applied them.