No description
  • C 95.9%
  • CMake 4.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
bivashy e81ab20d22 feat(cli): add --port= flag (#1)
Added:
  - `--port=` flag.

---

Example usage:
```
  ./containerdns --port=12345
```

Reviewed-on: #1
2026-09-18 16:43:11 +00:00
examples/dnsmasq chore: initial commit 2026-09-18 18:18:38 +05:00
.gitignore chore: initial commit 2026-09-18 18:18:38 +05:00
CMakeLists.txt chore: initial commit 2026-09-18 18:18:38 +05:00
docker_api.c chore: initial commit 2026-09-18 18:18:38 +05:00
docker_api.h chore: initial commit 2026-09-18 18:18:38 +05:00
LICENSE chore: initial commit 2026-09-18 18:18:38 +05:00
main.c feat(cli): add --port= flag (#1) 2026-09-18 16:43:11 +00:00
parse.c chore: initial commit 2026-09-18 18:18:38 +05:00
parse.h chore: initial commit 2026-09-18 18:18:38 +05:00
README.md feat(cli): add --port= flag (#1) 2026-09-18 16:43:11 +00:00

containerdns

A tiny DNS server that answers A queries with the IP of local Docker/Podman containers. The first label of the query name is matched against running container names, so mycontainer.internal.docker resolves to that container.

Build

Requires libcurl and json-c.

cmake -S . -B build
cmake --build build

Run

./build/containerdns [--bind-addr=127.0.0.1] \
  [--docker-sock=/var/run/docker.sock] \
  [--port=5353]
  • --bind-addr (default 127.0.0.1): address to listen on. Use the container bridge IP to serve containers directly.
  • --docker-sock (default /var/run/docker.sock): engine API socket. For rootless Podman use $XDG_RUNTIME_DIR/podman/podman.sock.
  • --port (default 5353): port to bind to DNS server. If default port is already in use.

The container table loads at startup and refreshes lazily on a lookup miss, at most once per second.

Test

dig @127.0.0.1 -p 5353 mycontainer.internal.docker

Setup

systemd service

Install the binary and run it on boot with /etc/systemd/system/containerdns.service:

[Unit]
Description=containerdns
After=network.target docker.service
Wants=docker.service

[Service]
ExecStart=/usr/local/bin/containerdns --bind-addr=127.0.0.1
Restart=on-failure

[Install]
WantedBy=multi-user.target
install -m755 build/containerdns /usr/local/bin/containerdns
systemctl daemon-reload
systemctl enable --now containerdns

systemd-resolved delegation

Forward a domain to containerdns without touching your global resolver. Create /etc/systemd/dns-delegate.d/docker.dns-delegate:

[Delegate]
DNS=127.0.0.1:5353
Domains=internal.docker

Then reload and verify:

systemctl restart systemd-resolved
resolvectl query mycontainer.internal.docker

Only names below internal.docker are sent to containerdns; everything else keeps using the normal upstream servers. See systemd.dns-delegate(5) for DefaultRoute and FirewallMark.

Without systemd-resolved

systemd.dns-delegate only exists in systemd-resolved (258+). Elsewhere, run a local forwarder for the internal.docker zone.

dnsmasq

/etc/dnsmasq.d/containerdns.conf:

server=/internal.docker/127.0.0.1#5353

Unbound

/etc/unbound/unbound.conf.d/containerdns.conf:

forward-zone:
    name: "internal.docker"
    forward-addr: 127.0.0.1@5353

BIND

In named.conf:

zone "internal.docker" {
    type forward;
    forward only;
    forwarders { 127.0.0.1 port 5353; };
};

Test with Alpine dnsmasq

examples/dnsmasq/ contains a minimal Containerfile, a dnsmasq.conf pointing at the Docker bridge gateway, and test.sh, which runs the whole chain (dig -> dnsmasq -> containerdns -> Docker API):

cmake -S . -B build && cmake --build build
./examples/dnsmasq/test.sh