Why host networking
The domain controller runs with network_mode: host. That is an unusual choice
for a containerised service, and it is deliberate.
A DC is not one service on one port
Section titled “A DC is not one service on one port”A Windows-compatible domain controller answers on a long list of ports: DNS (53), Kerberos (88), the RPC endpoint mapper (135), LDAP (389), SMB (445), kpasswd (464), LDAPS (636), the Global Catalog (3268/3269), and an on-demand dynamic RPC range starting at 49152. That last one is the problem.
The dynamic range is exactly that: dynamic. The RPC endpoint mapper hands a
client a high port at runtime, from a wide range. You cannot enumerate those
ahead of time to publish them with -p, and NAT through a bridge breaks the
referrals and address records the DC hands out, which point at the IP the DC
believes it has. The clean way to give the DC a coherent, real network identity
is to let it take the host’s network namespace and own those ports on the host’s
LAN IP, which is also exactly what a real Windows or Linux client expects when it
joins the domain.
What that costs
Section titled “What that costs”A host-networked container cannot also sit on a Docker bridge. So sibling containers (an app under test, the print server) cannot simply be “on the same Docker network” as the DC. They reach it instead through the host gateway:
services: my-app: networks: [samba] extra_hosts: - "host.docker.internal:host-gateway" dns: - 172.17.0.1 # host gateway = the DC's internal DNSnetworks: samba: external: trueThe samba bridge is the shared meeting point; the host gateway address is how
traffic crosses from that bridge to the host-networked DC.
The LDAPS caveat
Section titled “The LDAPS caveat”Plaintext paths can ride the host gateway, but TLS cannot use
host.docker.internal. Two things get in the way:
- The DC binds only loopback and its LAN IP. The host-gateway address (the
sambabridge gateway) has no listener on 636, so the hint only covers plaintext paths the gateway happens to forward. - The auto-generated certificate is
CN=dc1.<realm>with no subject alternative name, so aCERT_REQUIREDclient must connect by that exact name.
So for LDAPS you map the certificate name straight to the DC’s LAN IP and connect by it:
networks: [samba] extra_hosts: - "dc1.ad.supported.systems:10.22.22.22" # SAMBA_HOSTNAME.realm -> SAMBA_HOST_IP # LDAP URL: ldaps://dc1.ad.supported.systems:636Trust the DC’s CA at /var/lib/samba/private/tls/ca.pem.
The takeaway
Section titled “The takeaway”Host networking is what lets the DC behave like a real DC: coherent addressing, the full port set including the dynamic RPC range, and referrals that point somewhere a client can actually reach. The cost is that siblings talk to it through the host gateway rather than as Docker network peers, and TLS clients must connect by the certificate name. Both are predictable once you know why.