Skip to content

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 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.

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 DNS
networks:
samba:
external: true

The samba bridge is the shared meeting point; the host gateway address is how traffic crosses from that bridge to the host-networked DC.

Plaintext paths can ride the host gateway, but TLS cannot use host.docker.internal. Two things get in the way:

  1. The DC binds only loopback and its LAN IP. The host-gateway address (the samba bridge gateway) has no listener on 636, so the hint only covers plaintext paths the gateway happens to forward.
  2. The auto-generated certificate is CN=dc1.<realm> with no subject alternative name, so a CERT_REQUIRED client 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:636

Trust the DC’s CA at /var/lib/samba/private/tls/ca.pem.

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.