TechnitiumSoftware / DnsServer

Technitium DNS Server
https://technitium.com/dns/
GNU General Public License v3.0
4.47k stars 431 forks source link

Docker image base could potentially use the `chiseled` variant for approx 50% image size reduction #1012

Closed polarathene closed 1 month ago

polarathene commented 2 months ago

Just for awareness, MS offers slimmed down images when the bare essentials for the runtime are sufficient: https://github.com/dotnet/dotnet-docker/blob/main/documentation/ubuntu-chiseled.md

There are a few types at that link, I've not checked to see what is compatible with Technitium, but the mcr.microsoft.com/dotnet/runtime:8.0-noble-chiseled is only 87 MiB uncompressed compared to the base presently used that is 217MiB uncompressed:

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L1

The current image published is 270 MiB, so a rough estimate of around 140 MiB might be achievable. Something to consider :)

ShreyasZare commented 2 months ago

Thanks for the suggestion. Will test it out for the upcoming release.

ShreyasZare commented 1 month ago

Thanks again for your suggestion. I checked this and it wont be feasible to use that since this image does not include package manager and thus installing some dependencies wont be possible.

polarathene commented 1 month ago

thus installing some dependencies wont be possible.

Minimal images aren't meant to support such, they're for final stage only.

What do you need the package manager for? Is it required within the actual container? Or only to build the project? (in which case you can use build stage for that)


Is it these two? Does your project rely on them at runtime? Or only for something like tests?

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L15

I think you're building the actual project externally of the Dockerfile which is only packaging it as an image?:

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L17


You really should avoid VOLUME directive btw, it's generally an anti-pattern:

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L31

ShreyasZare commented 1 month ago

Is it these two? Does your project rely on them at runtime? Or only for something like tests?

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L15

The dnsutils package is required to be installed in the container so as to be able to debug issues using dig command which some users require to have.

The libmsquic package is required for the DNS server to use QUIC protocol to allow using DNS-over-QUIC and HTTP/3.

Both are thus required to be installed in the container.

I think you're building the actual project externally of the Dockerfile which is only packaging it as an image?:

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L17

Yes, the project is built locally and then the docker image is created.

You really should avoid VOLUME directive btw, it's generally an anti-pattern:

https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L31

Volume is required so that the DNS server's config file are persistently stored and reused.

polarathene commented 1 month ago

UPDATE: This comment is now redundant / outdated. See follow-up.


Both are thus required to be installed in the container.

Ah ok, both seem to be lacking slices for chisel atm, so until those are available those cannot be added.

Not sure why you'd need dig within the container itself to debug. You would still have the ports on the container that another container or the host (if ports published) could query. If you didn't need dig specifically, you could add in doggo or q for example, but generally I don't see the need (q has a docker image itself that you can use exactly for this sort of thing, I've done it in the past when debugging CoreDNS).

libmsquic has no way to bundle / static link into the build itself? If it's just a .dll or .so file you need, that could be taken.


Yes, the project is built locally and then the docker image is created.

👍


Volume is required so that the DNS server's config file are persistently stored and reused.

No it is not.

Please see my PR (or my prior ones it references) for more details: https://github.com/authelia/authelia/pull/7824

VOLUME is an anti-pattern. You should not use it, it is legacy directive from when there was no better options.

polarathene commented 1 month ago

TL;DR: I looked into this and it seems absolutely viable:


Earlier nitpick (ignore) Your package management is split across two `RUN` directives, so the final clean doesn't actually remove the added weight from the prior `RUN`: https://github.com/TechnitiumSoftware/DnsServer/blob/c88fd3c9a6270b9563511b84854d0e0f4154f605/Dockerfile#L10-L15 ```bash docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest technitium/dns-server ``` ![image](https://github.com/user-attachments/assets/83dde106-4f4e-4056-a9d4-17d1dca77355)

Switching to the chisel base image

I've taken some time to get more familiar with dotnet images, and from what I can understand the dotnet/runtime variant is probably not sufficient (EDIT: Confirmed). This project relies on dotnet/aspnet which I've learned adds another layer of runtime support needed (24MB extra on top of the runtime base of 72MB):

image

Beyond that there is the base system (glibc, ca-certificates, openssl) weighing in at about 14MB. While the project itself is only approx 6MB in weight.

libmsquic (verifying portability / compatibility)

libmsquic adds approx 14MB more (half if you don't require lttng for logging?):

$ ldd /usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.6/*.so | grep -B2 lttng

/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.6/libcoreclrtraceptprovider.so:
        linux-vdso.so.1 (0x00007ffef43a0000)
        liblttng-ust.so.0 => not found

It doesn't seem like it's linked correctly anyway, unless it's coming from somewhere else in your project? I could not find any .so linking to libmsquic. I'm only aware of your import for it as a system dependency (I am not a dotnet / C# dev to know how to verify further):

https://github.com/TechnitiumSoftware/DnsServer/blob/632e2b210d152eb3aacdbdbf53fd8db108868bc5/DnsServerCore/Dns/DnsServer.cs#L41

$ cd /usr/share/dotnet
$ fd '\.so$' --exec ldd | grep lttng
        liblttng-ust.so.0 => not found

# No output:
$ fd '\.so$' --exec ldd | grep quic
curl https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb --output packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb
# Installs both `libmsquic` and `libnuma`:
apt update && apt install libmsquic

# Related libs installed:
$ ls -l /usr/lib/x86_64-linux-gnu | grep -E 'lib(msquic|numa)'
-rwxr-xr-x  1 root root  7172344 Mar 12  2024 libmsquic.lttng.so.2.3.5
lrwxrwxrwx  1 root root       18 Mar 12  2024 libmsquic.so.2 -> libmsquic.so.2.3.5
-rwxr-xr-x  1 root root  6583256 Mar 12  2024 libmsquic.so.2.3.5
lrwxrwxrwx  1 root root       16 Dec 26  2022 libnuma.so.1 -> libnuma.so.1.0.0
-rw-r--r--  1 root root    52312 Dec 26  2022 libnuma.so.1.0.0

# Requirements (from the current published image of `technitium/dns-server`):
$ ldd /usr/lib/x86_64-linux-gnu/libmsquic.so.2
        linux-vdso.so.1 (0x00007fffc75c8000)
        libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f6f8191c000)
        libnuma.so.1 => /lib/x86_64-linux-gnu/libnuma.so.1 (0x00007f6f8190f000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6f8172e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f6f81f11000)

$ ldd /usr/lib/x86_64-linux-gnu/libnuma.so.1
        linux-vdso.so.1 (0x00007ffc197b4000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efedeeee000)
        /lib64/ld-linux-x86-64.so.2 (0x00007efedf0e0000)

# As mentioned earlier `liblttng-ust` is missing, that affects this lib too thus is probably not loaded/used by `technitium/dns-server`?:
$ ldd /usr/lib/x86_64-linux-gnu/libmsquic.lttng.so.2.3.5
        linux-vdso.so.1 (0x00007ffc365e8000)
        liblttng-ust.so.1 => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe64d5d6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe64d950000)

Check the minimum glibc version these external libs will support:

# Inspect symbols for glibc version dependencies (filtering to the highest semver found):
$ readelf --dyn-syms --version-info --wide /usr/lib/x86_64-linux-gnu/libmsquic.so.2 \
  | grep 'Name: GLIBC' \
  | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' \
  | sort -t . -k1,1n -k2,2n \
  | tail -n 1
2.34

$ readelf --dyn-syms --version-info --wide /usr/lib/x86_64-linux-gnu/libnuma.so.1 \
  | grep 'Name: GLIBC' \
  | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' \
  | sort -t . -k1,1n -k2,2n \
  | tail -n 1
2.33

# Unlike the proposed Ubuntu chisel image, this one is Debian 12 based like the repo used for libs:
$ docker run --rm -it mcr.microsoft.com/dotnet/aspnet:8.0
ldd (Debian GLIBC 2.36-9+deb12u7) 2.36
Copyright (C) 2022 Free Software Foundation, Inc.

# Ubuntu chisel variant will be based off either 22.04 or 24.04
$ docker run --rm -it ubuntu:24.04 ldd --version
ldd (Ubuntu GLIBC 2.39-0ubuntu8.1) 2.39
Copyright (C) 2024 Free Software Foundation, Inc.

# Even if 22.04, the glibc min requirement will be supported:
$ docker run --rm -it ubuntu:22.04 ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.7) 2.35
Copyright (C) 2022 Free Software Foundation, Inc.

Refactored Dockerfile (chiseled)

Now we have enough information to work with.

Here is a proposed refactor I could PR (Added extra context with comments and revised your EXPOSE + LABEL directives too):

# syntax=docker.io/docker/dockerfile:1

## This stage is only used to support preparing the runtime-image stage
FROM ubuntu:24.04 AS deps
RUN <<HEREDOC
  # Add the MS repo to install libmsquic (which also adds libnuma):
  apt update && apt install -y curl
  curl https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb --output packages-microsoft-prod.deb
  dpkg -i packages-microsoft-prod.deb
  rm packages-microsoft-prod.deb
  apt update && apt install -y libmsquic
  apt clean -y

  # Workaround for `COPY` semantics to preserve symlinks you must copy at the directory level:
  # https://github.com/moby/moby/issues/40449
  mkdir /runtime-deps
  mv /usr/lib/x86_64-linux-gnu/libmsquic.so* /runtime-deps
  mv /usr/lib/x86_64-linux-gnu/libnuma.so* /runtime-deps
HEREDOC

## Published image - No shell or package manager (only what is needed to run the service)
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled AS runtime-image
COPY ./DnsServerApp/bin/Release/publish/ /opt/technitium/dns
# DNS-over-QUIC support (libmsquic):
COPY --link --from=deps /runtime-deps/ /usr/lib/x86_64-linux-gnu/

# Graceful shutdown support:
STOPSIGNAL SIGINT

# `/etc/dns` is expected to exist:
WORKDIR /etc/dns
WORKDIR /opt/technitium/dns

ENTRYPOINT ["/usr/bin/dotnet", "/opt/technitium/dns/DnsServerApp.dll"]
CMD ["/etc/dns"]

## Only append image metadata below this line:
EXPOSE \
  # Standard DNS service
  53/udp 53/tcp      \
  # DNS-over-QUIC (UDP) + DNS-over-TLS (TCP)
  853/udp 853/tcp    \
  # DNS-over-HTTPS (UDP => HTTP/3) (TCP => HTTP/1.1 + HTTP/2)
  443/udp 443/tcp    \
  # DNS-over-HTTP (for when running behind a reverse-proxy that terminates TLS)
  80/tcp 8053/tcp    \
  # Technitium web console + API (HTTP / HTTPS)
  5380/tcp 53443/tcp \
  # DHCP
  67/udp

# https://specs.opencontainers.org/image-spec/annotations/
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="Technitium DNS Server"
LABEL org.opencontainers.image.vendor="Technitium"
LABEL org.opencontainers.image.source="https://github.com/TechnitiumSoftware/DnsServer"
LABEL org.opencontainers.image.url="https://technitium.com/dns/"
LABEL org.opencontainers.image.authors="support@technitium.com"

Compare and verify:

# Build it, over 50% reduction + better security (_if compromised the container has no shell or package manager_):
$ docker build --tag local/technitium-dns .
$ docker image ls | grep technitium
local/technitium-dns                    latest                 9afacf70571c   18 minutes ago   123MB
technitium/dns-server                   latest                 bfa2e67a3b19   3 months ago     270MB

# Roughly accurate compressed size on DockerHub:
$ docker save local/technitium-dns | gzip -c | wc -c | numfmt --to iec
50M
$ docker save technitium/dns-server | gzip -c | wc -c | numfmt --to iec
110M

# Quick test that everything runs:
$ docker run --rm -it --hostname example.test local/technitium-dns
Technitium DNS Server was started successfully.
Using config folder: /etc/dns

Note: Open http://example:5380/ in web browser to access web console.

Press [CTRL + C] to stop...

Additional context

If you want to test that out quickly, you can build that image and COPY the /opt from an existing release:

-COPY ./DnsServerApp/bin/Release/publish/ /opt/technitium/dns
+COPY --link --from=technitium/dns-server /opt/technitium/dns /opt/technitium/dns

Likewise, you can compare image sizes for savings (or use an ARG to optionally build with current base that has a package manager + shell), this would produce a 230MB image (vs the 270MB of the current image):

-FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled
+FROM mcr.microsoft.com/dotnet/aspnet:8.0
ShreyasZare commented 1 month ago

Thanks for the analysis and the PR. Really appreciate the efforts.

Having dig is a user requirement since they want to have it inside the container to debug resolution issues from the container's vantage point. There is already DNS Client tool available in the web admin panel for testing but some users demand dig so cannot substitute it for anything else.

The libmsquic is required by the dotnet runtime to support QUIC and is not directly referenced in the DNS server project. The issue with libmsquic is that its still quite unstable and has several issues already when they have new updates which makes it stop running on existing or new setups. The approach you specify here will require detailed analysis every now and then when the docker build fails to support QUIC protocol. There can be changes in dependencies that libmsquic require and they may change certain aspects which would need to be figured out. Which is why I would still rely on the official package that the provide directly. Its really not feasible to spend a couple of days to find a fix for such issues.

Regarding volume, I am not really good with docker but is it not required to have volume when upgrading to new image? Its really important and a hard requirement to have persistent storage for the DNS server to avoid any operational issue. Not having persistent storage will cause severe issues especially if someone has their zones signed with DNSSEC.

Even if its possible to do away with volumes, not having it will cause multiple support emails everyday since users are expecting to have it. Most users are not good at docker too and this would increase a lot of support requests.

polarathene commented 1 month ago

This is a large reply again (sorry about that), split into dig, libmsquic and VOLUME sections.

If you're short on time, there is no pressure to respond to this in a hurry, or at all. While this improvement isn't too important to me personally but I've done my best to address your concerns / feedback. I'll respect your decision regardless 👍

dig - Weighing up it's inclusion / support vs alternatives

Having dig is a user requirement

Is dig specifically a requirement, or any capable tool?

since they want to have it inside the container to debug resolution issues from the container's vantage point

That doesn't really make much sense to me vs within the same network/host, but uhh alright? It's still possible to do without bundling one into the image (not that I'm against that).

There is already DNS Client tool available in the web admin panel for testing but some users demand dig so cannot substitute it for anything else.

Who are these users to enforce dig?

I'm all for supporting users when it makes sense, but if they want to insist on a non-project specific dependency for an image, they could opt to build their own image? This is generally the stance I have with images I maintain and users request something for their convenience.

Here's what that would look like:

FROM local/technitium-dns
RUN apt update && apt install -y dnsutils
# Build proposed image with alternative base image via build arg
# A little bit more awkward in this case since your `Dockerfile` expects a pre-built folder to be provided,
# instead of having a `Dockerfile` / stage that can build within a containerized environment.
docker build \
  --build-arg RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0 \
  --tag local/technitium-dns \
  .

# Separate Dockerfile to build for adding dig (simple to maintain):
docker build --tag local/technitium-dns:with-dig -f Dockerfile.dig .

QUIC compatibility is dependent upon /usr/lib/x86_64-linux-gnu/ being valid, but they'll likely be happy with the current aspnet image.

Perhaps that's not acceptable for supporting such users, well then how about no extra files or build commands?

# Adapt below example to real equivalent compose config:
# https://github.com/TechnitiumSoftware/DnsServer/blob/master/docker-compose.yml
services:
  dns-server:
    image: local/technitium-dns:with-dig
    hostname: example.test
    pull_policy: build
    build:
      dockerfile_inline: |
        # syntax=docker/dockerfile:1

        FROM mcr.microsoft.com/dotnet/aspnet:8.0
        COPY --link --from=technitium/dns-server:latest /opt/technitium/dns /opt/technitium/dns
        COPY --link --from=technitium/dns-server:latest /usr/lib/x86_64-linux-gnu/libmsquic.so* /usr/lib/x86_64-linux-gnu/
        COPY --link --from=technitium/dns-server:latest /usr/lib/x86_64-linux-gnu/libnuma.so* /usr/lib/x86_64-linux-gnu/
        WORKDIR /etc/dns
        STOPSIGNAL SIGINT
        ENTRYPOINT ["/usr/bin/dotnet", "/opt/technitium/dns/DnsServerApp.dll"]
        CMD ["/etc/dns"]
        RUN apt update && apt install -y dnsutils
$ docker compose up

dns-server-1  | Technitium DNS Server was started successfully.
dns-server-1  | Using config folder: /etc/dns
dns-server-1  |
dns-server-1  | Note: Open http://example.test:5380/ in web browser to access web console.
dns-server-1  |
dns-server-1  | Press [CTRL + C] to stop...

Voila, dig on a much larger image. The users that insist on dig get what they want at the cost of 15 lines to compose.yaml (less if they're ok with two step builds and separate Dockerfile).

Here's what they could do though instead if they weren't stubborn with dig for some bizarre reason:

# Download and extract `q` to `/tmp/q`:
curl -fsSL https://github.com/natesales/q/releases/download/v0.19.2/q_0.19.2_linux_amd64.tar.gz | tar -xz -C /tmp q
# This will work with whatever image (`q` is static linked, no external libs needed):
$ docker run --rm -it --name dns-server -v /tmp/q:/usr/local/bin/q technitium/dns-server

# Run `q` to query the nameserver at 127.0.0.1 for `github.com` A record:
$ docker exec -it dns-server /usr/local/bin/q github.com @127.0.0.1 A
github.com. 40s A 4.237.22.38

That is way simpler, but wait, there's more:

services:
  dns-server:
    image: local/technitium-dns:with-dig
    hostname: example.test
    pull_policy: build
    build:
      dockerfile_inline: |
        # syntax=docker/dockerfile:1
        FROM fedora AS q
        RUN curl -fsSL https://github.com/natesales/q/releases/download/v0.19.2/q_0.19.2_linux_amd64.tar.gz | tar -xz -C /tmp q

        FROM technitium/dns-server
        COPY --link --from=q /tmp/q /usr/local/bin/q

Self-contained in the compose.yaml as an extension of the official image if volume mounting a binary on the host temporarily is a deal-breaker.

Adding q is less than 4MB in weight, you could bundle that without a problem. By contrast supporting dig is more hassle to keep the min size down, so the easy way bloats image size by 100MB+.

Why would someone want to prefer q for troubleshooting over dig? Let's take a look at the feature comparison:

image

So dig isn't helpful with any of the other DNS protocols, while it also lacks elsewhere:

image

I've used the YAML / JSON outputs in the past to process into CSV or Markdown tables. I don't think dig would have been anywhere near as useful at troubleshooting the dns issues I've dealt with in the past, so really it's just a familiarity / preference comfort these users have for dig.


You should not bloat the image size just for their benefit when there are a variety of ways they can have their needs met as shown above, even if they insist on dig being vital within the image. If you really want to cater to that audience but would still value the proposed size reduction and other improvements, then publish a separate tag for the dig variant where you build the separate Dockerfile image on behalf of the user and maintain that burden on our end.

Alternatively, if the above is not convincing and you find it easier for you personally to maintain, stay with the 270MB image for a 6MB project (+100MB dotnet runtime, but that base layer can be shared across dotnet projects). The audience for Docker is for many other benefits, optimizing on size is just a bonus there, so I totally understand if you decide to not move forward with the slimmer image.


libmsquic concerns

The libmsquic is required by the dotnet runtime to support QUIC and is not directly referenced in the DNS server project. The issue with libmsquic is that its still quite unstable and has several issues already when they have new updates which makes it stop running on existing or new setups.

Yeah seems to be documented in relation to building System.Net.Quic?

As noted, the library you've already got in the existing image has lttng variant for libmsquic already missing a dependency. So long as you don't use anything that depends on that I assume you're fine. I don't really see anything I've done that will warrant concern though.

The approach you specify here will require detailed analysis every now and then when the docker build fails to support QUIC protocol. There can be changes in dependencies that libmsquic require and they may change certain aspects which would need to be figured out.

Has that actually occurred? Can you reference anything? The dependencies that library has is glibc + openssl, the other dependency is one installed by the DEB package.

I am aware of the OpenSSL 1.x to 3.x transition, but that's to be expected. Releases provide both variants, it's not really an issue to worry about. Glibc compatibility is usually the one most run into if a library or project builds with a version of glibc that is too new.

I'm still using the same mcr.microsoft.com/dotnet/aspnet image, just a variant tag they offer which happens to swap out the OS base distro from Debian 12 to Ubuntu, but you would expect compatibility unless otherwise cautioned by the publisher (Microsoft) for such an important image.

Likewise libmsquic is dotnet and MS backed, I would be rather surprised for anything to go horribly wrong there that isn't resolved upstream, given all the enterprise support.

If there wasn't as much alignment going on with those concerns, I'd agree that with the increased risk / hassle to maintain. I'm just not really seeing that here (_and reverting in such an event is as simple as changing the base image back, you could effectively use mcr.microsoft.com/dotnet/aspnet:8 instead of ubuntu:24.04 as the deps base and remove the separate stage + mv lines and you're back to what you have presently (but slightly more efficient in size).

Which is why I would still rely on the official package that the provide directly. Its really not feasible to spend a couple of days to find a fix for such issues.

I am still relying on the official package? All that's being done is moving the libs it installed to the runtime image stage.

I don't think it'll take you multiple days to find and fix such issues. I have no experience with your project or dotnet. I provided very helpful commands for you to get an exact breakdown, just ask me questions if something still feels uncomfortable there.

I did take a while to learn how to do that and get comfortable with it, so I realize I'm more confident but as far as external deps go with libmsquic identifying the relevant information is covered above for you. It'll be the same process if something were to change, and if it did prove to be a problem you just revert to this:

# syntax=docker.io/docker/dockerfile:1

FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN <<HEREDOC
  # Add the MS repo to install libmsquic (which also adds libnuma):
  apt update && apt install -y curl
  curl https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb --output packages-microsoft-prod.deb
  dpkg -i packages-microsoft-prod.deb
  rm packages-microsoft-prod.deb
  apt update && apt install -y libmsquic
  apt clean -y

  mkdir /etc/dns
HEREDOC

WORKDIR /opt/technitium/dns
COPY ./DnsServerApp/bin/Release/publish/ /opt/technitium/dns/

# Graceful shutdown support:
STOPSIGNAL SIGINT

ENTRYPOINT ["/usr/bin/dotnet", "/opt/technitium/dns/DnsServerApp.dll"]
CMD ["/etc/dns"]

All the 2nd FROM is doing is switching to the minimal variant of that image from MS (for size and reduced attack surface), and copying over the relevant libmsquic lib with it's libnuma dependency.


VOLUME directive

Regarding volume, I am not really good with docker but is it not required to have volume when upgrading to new image?

A volume can be named or anonymous types that Docker manages for you (which you have some insight into via docker volume ls or better insight with the Docker Desktop GUI). These externalize storage from the container to persist data when the container is gone.

Technically anonymous type will not persist after the container exits if you used --rm with docker run ..., but docker compose up has a different behaviour that tries very hard to persist it, so much that it's problematic to get a "clean slate" if you wanted to run the container as if it was new and this storage was reset.

A container itself so long as it's not removed/destroyed will maintain any temporary state associated to it when it restarts or is stopped/started. The container is tied to the image it was run from however, so yes you're correct that without volumes to persist this internal container filesystem will be flushed during an image upgrade as a new image results in a new container instance.

VOLUME is problematic in that you can't really opt-out of it. If you do not provide your own volume it will force creating an anonymous volume for EVERY new container instance. So if you used docker run ... without --rm and ctrl + c to stop the container, without a --name assigned to start that same container again, running the docker run ... command another time will create a new instance. You cannot easily identify anonymous volumes via docker volume ls, which is problematic with Docker Compose usage since you'd need to know the random ID to remove them manually.

My link on the subject has more thorough details on these issues along with links to references. You can avoid VOLUME directive and all those caveats go away.

Its really important and a hard requirement to have persistent storage for the DNS server to avoid any operational issue. Not having persistent storage will cause severe issues especially if someone has their zones signed with DNSSEC.

I understand this. As should anyone who is serious about deployment with Docker. Relying on implicit anonymous volumes is not wise. If someone wants to persist their state they should do so with an explicit volume mount (be that a named, anonymous or another type, most commonly a bind mount).

I want to know that your Docker Compose example config configures a volume explicitly, this has no relation to the VOLUME directive in a Dockerfile, it replaces it. When the container is started with another volume at the location, the implicit anonymous VOLUME will not be used as a fallback.

https://github.com/TechnitiumSoftware/DnsServer/blob/632e2b210d152eb3aacdbdbf53fd8db108868bc5/docker-compose.yml#L41-L42

https://github.com/TechnitiumSoftware/DnsServer/blob/632e2b210d152eb3aacdbdbf53fd8db108868bc5/docker-compose.yml#L47-L48

That is a named volume (you can reference it by an actual name, as opposed to anonymous kind). When you don't want to think much about storage and where it's located, named volumes are useful. You also don't have to think about interaction with the host filesystem like some do with bind volume mounts due to containers writing UID/GID from what the container user is, vs expectation of a user on the host system (where they may not be able to easily delete/move files for different UID/GID than their own, so some projects implement workarounds to appease those users 🤷‍♂️ )

Bind mounts are like this:

    volumes:
      - ./config:/etc/dns

Now a config/ folder will be created when the container starts, or if one exists already it will use that. If Docker runs as a different UID/GID than would have been used with mkdir command on the host, there will be a difference in ownership, which for some projects (like containers running as non-root UID/GID) that may trigger a permissions error early on (container can't write to the location).

Using named volumes like your config example has avoids that, while bind mounts are a bit more transparent to the user what's being stored (they can easily view the contents from their file browser / CLI), but for container state that doesn't really need interaction from the user, just persistence a named volume is more convenient to prefer.

Even if its possible to do away with volumes, not having it will cause multiple support emails everyday since users are expecting to have it. Most users are not good at docker too and this would increase a lot of support requests.

I disagree. I've already outlined various ways that VOLUME in a Dockefile can cause you and users problems. You don't want to troubleshoot such.

Docker volumes are good, persisting data outside of the container is good. No disagreement there. But it should be explicit. I'm sure you can agree that implicit is not ideal. How does someone go about backing up this anonymous volume that's not visible to them in config or command? They take a risk that they're not aware of that bites them later on.

Anyone that doesn't really know how to use Docker could misconfigure it in various ways (I see this all the time for docker specific project I maintain), you do not have to support them for free because they don't want to put effort in. You've got a Docker Compose example which is what those users should be leveraging, it already configures a named volume so VOLUME isn't serving any purpose here, you can verify this yourself without VOLUME and tagging two different images locally to switch between, it might help you feel more at ease to witness that it'll be ok.

I have maintained docker-mailsever since early 2021 and contributed to quite a few projects with Docker related contributions, including to the Docker project itself. I appreciate your patience and hopefully all of this alleviates the valid concerns you have raised.

ShreyasZare commented 1 month ago

Is dig specifically a requirement, or any capable tool?

There is already a tool available in the admin panel but some users require dig too for debugging issues since dig is the standard tool in DNS world.

That doesn't really make much sense to me vs within the same network/host, but uhh alright? It's still possible to do without bundling one into the image (not that I'm against that).

Its usually not used to see what certain domain resolves to. Its to see why a certain domain is failing to resolve from the DNS server's IP address. Its used debugging scenarios where there can be certain config such that only the specific IP address is permitted or that there is split horizon setup where the source IP is important.

Sometimes, users also need other tools like tcpdump to check network traffic or tail command to just check the logs file while they are testing. I guess the chiseled variant would remove all such tools too and since there is no package manager, installing any specific tool too would become difficult.

Who are these users to enforce dig?

There are several users who have requested it. It includes an ISP as well.

I'm all for supporting users when it makes sense, but if they want to insist on a non-project specific dependency for an image, they could opt to build their own image? This is generally the stance I have with images I maintain and users request something for their convenience.

Its really not feasible for many users to build their own image. The just have operational staff and do not have any expertise in such things. Usually such things ends up being a support email.

Its really not about the feature some tool has over dig. Its that they need dig to debug issues and they do not need any additional protocol support.

Your suggestions regarding libmsquic are good but the only concern I have is that if there are any changes and it breaks the build then it will be in the middle of software release process and debugging the issue at the time would be time consuming. The project is developed and deployed when I have time available and such issues can become a problem at the wrong time.

Regarding the VOLUME directive, will test it out by removing it and see how it goes. As long as the user is able to follow the same flow, I don't think there would be any issue.

Overall, my take here is that I would be ok with a larger image size as a tradeoff just to so that users have what they need and if there is a need, they should be able to get console access to the container and be able to debug issues with any tool that they may want to install.

polarathene commented 1 month ago

TL;DR:

I think at this point this topic has covered everything we were both interested about clarifying/discussing?

My takeaway is that it'll be easier for you to support just a single Dockerfile that you publish images for, and that you've established why it should be the larger image. I can PR only the improvements, excluding the changes related to adopting a chiseled base image (or that could be a separate Dockerfile in the repo for discovery to users interested in building a minimal image themselves).


There is already a tool available in the admin panel but some users require dig too for debugging issues since dig is the standard tool in DNS world.

That doesn't really justify dig as the right tool given the tradeoffs I discussed previously. It's not like dig can't be used from outside of the container on the host or via another a container, it's not a big ask for users wanting such to add a few lines for a custom build that adds dig, especially when it's rare that they'd need dig in the container (assuming their usage of this image doesn't primarily involve only troubleshooting with dig, I would hope not).

Its usually not used to see what certain domain resolves to. Its to see why a certain domain is failing to resolve from the DNS server's IP address.

I understand, but without a specific example I chose something that was minimal so the functionality was shown as working. It queried 127.0.0.1 which should have been the Technitium DNS server listening at port 53 right? If I had a configured the service so that resolved github.com A record to some private range IP, we'd have received that instead.

I use split horizon myself for some public domains to resolve to private range IPs instead. I've also used it for containers in a common network to use as their DNS service for mail server records (which Docker's default network settings for it's embedded DNS lacks configuration for), along with forwarding everything else to another resolver like 1.1.1.1.

A few months ago I needed to investigate why I was having problems with TXT records, github.com is actually one where it was a problem. The response or record IIRC exceeded a limit for UDP so it receives a response to query again with TCP, only my local DNS service could do that, while the local routers DNS would fail to perform TCP queries. It was further complicated due to some Windows behaviour with 0.0.0.0 as a fallback DNS IP I never explicitly configured that was introducing racey behaviour between my own DNS service running in WSL2 vs the router at the time, with a public domain being inconsistent / unreliable as a result.

I initially tried to use dig and drill, but found doggo and q to be far more helpful investigating that.


Sometimes, users also need other tools like tcpdump to check network traffic or tail command to just check the logs file while they are testing. I guess the chiseled variant would remove all such tools too and since there is no package manager, installing any specific tool too would become difficult.

Yes, that can be an issue, but again this specifically for troubleshooting purposes, not functionality of the service itself. I think that distinction is important and that there should be no need for a shell or package manager within a container deploying the service, having access to such in production does not really help security if the container were compromised in some manner?

What you'll see with Google Distroless images is they offer a :debug image variant that adds busybox for a shell and many common commands. Other images may offer a larger variant, as we see with dotnet:aspnet. It's then down to as I showed in my previous response:

In this context, you'll know your users much better than I do. If they're often reporting bugs and citing they're using Docker and that usually requires reaching out to dig or instructing them how to install other tools in the container, then you know that this is important for you, not just your users, as making your job to provide support easier is vital.

For reference, while it would be possible, I chose not to add dig with the same approach as libmsquic due to the much larger dependency list. I would not be comfortable with that, even if would resolve the dig requirement:

ldd output for dig ```console $ ldd $(which dig) linux-vdso.so.1 (0x00007ffe1f1e4000) libisc-9.18.28-0ubuntu0.24.04.1-Ubuntu.so => /lib/x86_64-linux-gnu/libisc-9.18.28-0ubuntu0.24.04.1-Ubuntu.so (0x00007fd99e980000) libdns-9.18.28-0ubuntu0.24.04.1-Ubuntu.so => /lib/x86_64-linux-gnu/libdns-9.18.28-0ubuntu0.24.04.1-Ubuntu.so (0x00007fd99e76c000) libisccfg-9.18.28-0ubuntu0.24.04.1-Ubuntu.so => /lib/x86_64-linux-gnu/libisccfg-9.18.28-0ubuntu0.24.04.1-Ubuntu.so (0x00007fd99e734000) libirs-9.18.28-0ubuntu0.24.04.1-Ubuntu.so => /lib/x86_64-linux-gnu/libirs-9.18.28-0ubuntu0.24.04.1-Ubuntu.so (0x00007fd99e72e000) libbind9-9.18.28-0ubuntu0.24.04.1-Ubuntu.so => /lib/x86_64-linux-gnu/libbind9-9.18.28-0ubuntu0.24.04.1-Ubuntu.so (0x00007fd99e716000) libidn2.so.0 => /lib/x86_64-linux-gnu/libidn2.so.0 (0x00007fd99e6f2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd99e4e0000) libuv.so.1 => /lib/x86_64-linux-gnu/libuv.so.1 (0x00007fd99e4ab000) libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007fd99e401000) libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007fd99deee000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fd99ded2000) libjson-c.so.5 => /lib/x86_64-linux-gnu/libjson-c.so.5 (0x00007fd99debc000) libnghttp2.so.14 => /lib/x86_64-linux-gnu/libnghttp2.so.14 (0x00007fd99de91000) libxml2.so.2 => /lib/x86_64-linux-gnu/libxml2.so.2 (0x00007fd99dcaf000) /lib64/ld-linux-x86-64.so.2 (0x00007fd99ea3d000) libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007fd99dc5b000) libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007fd99db92000) libmaxminddb.so.0 => /lib/x86_64-linux-gnu/libmaxminddb.so.0 (0x00007fd99db89000) liblmdb.so.0 => /lib/x86_64-linux-gnu/liblmdb.so.0 (0x00007fd99db71000) libns-9.18.28-0ubuntu0.24.04.1-Ubuntu.so => /lib/x86_64-linux-gnu/libns-9.18.28-0ubuntu0.24.04.1-Ubuntu.so (0x00007fd99db22000) libunistring.so.5 => /lib/x86_64-linux-gnu/libunistring.so.5 (0x00007fd99d975000) libicuuc.so.74 => /lib/x86_64-linux-gnu/libicuuc.so.74 (0x00007fd99d768000) liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007fd99d734000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd99d64b000) libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007fd99d61f000) libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007fd99d619000) libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007fd99d60c000) libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007fd99d603000) libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fd99d5f0000) libicudata.so.74 => /lib/x86_64-linux-gnu/libicudata.so.74 (0x00007fd99b890000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fd99b613000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd99b5e6000) ```

Its really not feasible for many users to build their own image. The just have operational staff and do not have any expertise in such things. Usually such things ends up being a support email.

I don't see why? If they can use a docker-compose.yml / compose.yaml, they can do exactly what I showed above. I assume that you're referring to users relying on other platforms like Portainer.

For projects I maintain, it's not realistic to support every user in this manner, so we have a clear policy about this (and docker-mailserver is exclusively container only deployment). High value/demand requests are supported, but anything with only a couple users expressing interest is deferred to community contributions to our docs/examples as guides for niche requirements.

You mention support email quite often. I don't provide any email support address myself, and if I did it certainly would not be for any free support. Paid support would be quite different, and it's not clear to me if you're taking on a burden for your users benefits if they're using OSS release and expecting dedicated support for free.

I could not afford the time for such personally, all support I do is public so that common support requests can be linked to previous issues (if the user did not use search first), or they are to rely on the community helping them at Github Discussions.

You've mentioned installing additional packages, I'm not sure how much overlap that is for the dig is needed vs "not able to build a custom image". Just so that we're clear, building a custom image does not require publishing it to a registry, as shown above it can be done completely locally.


Who are these users to enforce dig?

There are several users who have requested it. It includes an ISP as well.

Are you certain that they'd be against an alternative? As shown q has broader support for troubleshooting DNS, it does have standard dig output format, and the syntax is roughly the same IIRC, it really shouldn't be too big of an ask and they may be understanding of why dig would be replaced by q.

A small addition to the README / docs could further simplify that. I noticed your DockerHub page doesn't really have any container specific guidance, I'm not sure if you specifically document dig being pre-installed anywhere either?

Its really not about the feature some tool has over dig. Its that they need dig to debug issues and they do not need any additional protocol support.

They don't need additional protocol support... until they're troubleshooting exactly that...? Does the other protocol support work well that you get no bug reports about it? (understandably it's probably used much less than :53 from the type of users you're describing) How do you advise they troubleshoot in that scenario?

I don't think they need dig. I'm not here to push you further on the subject though, I was only interested in the pragmatic reasons from both a user and maintainer perspective, along with the pros/cons of such a choice. I understand that supporting dig within the image is a clear deal-breaker for you and will leave it at that.


Your suggestions regarding libmsquic are good but the only concern I have is that if there are any changes and it breaks the build then it will be in the middle of software release process and debugging the issue at the time would be time consuming. The project is developed and deployed when I have time available and such issues can become a problem at the wrong time.

Ok, but your actual project is developed and release without a dependency upon Docker. It should not need to block you, worst case is publishing an updated image is delayed until you have time to resolve that.

The problem you're concerned with could happen in various ways already. As you've said libmsquic is not stable/reliable in it's development and has broken in the past. You still risk that concern regardless of the proposed change here.

Likewise the base image you rely on, some breaking change could happen there (they move away from Debian, or just upgrade to a newer release with Debian and something breaks packaging wise for you, with docker-mailserver this happened for Dovecot package which regressed in version available, we also have to account for packages that upgrade to new major versions with breaking changes, etc).

Even Docker itself has various caveats support wise in the past. docker-mailserver still gets reports with EOL versions of Docker being used that have some bugs or other limitations, that is the users concern to rely on software that is over 5 years old and the issues that come with that, so we do not offer support.

Soon there will be a new release with Containerd 2.0, which introduces some breaking changes that are an improvement/fix, but some enterprise software is known to crash/fail as it relied on misconfiguration bugs to function instead of handling it properly in their own code (Envoy for example did not even document the implicit expectation).

So while I do understand your concern here, I've tried to be clear that the impact towards such really should be minimal. I provided an example of what an immediate fix would look like (switches back to the full dotnet:aspnet image.


Regarding the VOLUME directive, will test it out by removing it and see how it goes. As long as the user is able to follow the same flow, I don't think there would be any issue.

👍

Overall, my take here is that I would be ok with a larger image size as a tradeoff just to so that users have what they need and if there is a need, they should be able to get console access to the container and be able to debug issues with any tool that they may want to install.

👍

Ok then. If the size / security benefits of the slimmer image still interests you, you could publish it as a variant like suggested. Or so that maintenance is simpler for you, only support publishing Docker via the larger image.

If you'd like the PR to be adjusted to something like Dockerfile.chisel or similar to keep in the repo for users to discover I could do that. It's less useful to your users directly as-is without a Dockerized build environment, or could be adjusted to COPY the /opt directory from a published image instead.

Otherwise feel free to close the PR. I am ok with whatever you decide :)

ShreyasZare commented 1 month ago

Thanks for the response. I many not agree with things but really appreciate this feedback.

The reason for having dig is to debug issues related to resolution. These are not really issues between the clients and the DNS server but between the DNS server and name servers queried during recursive resolution. So the users are not querying the DNS server itself, but want to query name servers from the same container to find if there are any network issues or if the name server is giving the same response with dig too.

Dig is standard tool made by BIND developers and its preferred for correctness of the response and RFC compliance. Knowing how hard it is to implement recursive resolution, I am myself not convinced to replace dig with any other tool which is quite new. If I had to convince user to ditch dig then I would prefer to convince them to use the DNS client tool on the panel instead since I am not confident if a new tool will always give correct results for edge cases which may cause support tickets.

Having a debug image is not useful since user cannot switch images in production when they need to debug. The debugging has to happen in the same container since it has to use the same source IP to be able to replicate the issue.

Regarding "libmsquic", the uncertainty with it is an issue. Currently, if the latest libmsquic version fails to work, I can always install the last known good version from their repo and build the image. With the setup you propose, I am confident that it would break within a couple of months. The library devs can change install paths, or rename the files, have different dependencies, or have a dependency of some other lib with specific version. Its unpredictable what may happen. The QUIC support is already provided as a preview feature in dotnet and later releases may change anything including the API.

I have to manually check each image that gets build before it gets released to ensure that QUIC is working. There is no guarantee that QUIC will work even when the libmsquic package is installed correctly. In such case, I have to downgrade to earlier version, built the image, and test it manually again to see if QUIC works before releasing the image.

You mention support email quite often. I don't provide any email support address myself, and if I did it certainly would not be for any free support. Paid support would be quite different, and it's not clear to me if you're taking on a burden for your users benefits if they're using OSS release and expecting dedicated support for free.

While this project is OSS, its deployed to several paying clients at my full time job too. There are also some users paying for support.

I could not afford the time for such personally, all support I do is public so that common support requests can be linked to previous issues (if the user did not use search first), or they are to rely on the community helping them at Github Discussions.

I try to answer all support queries as the project is not yet popular and lacks documentation. I am hoping that the community gets bigger over time, here and on reddit, so that will spend less time with queries.

polarathene commented 1 month ago

Thanks for the response. I many not agree with things but really appreciate this feedback.

❤️

I should probably bow out of the discussion as I think we are both too polite and feel a need to respond fully? 😆

I will send you another PR with improvements to the Dockerfile without the changes you have raised concerns over.

UPDATE: Done

dig

The reason for having dig is to debug issues related to resolution. Dig is standard tool made by BIND developers and its preferred for correctness of the response and RFC compliance.

Yes I'm aware. You do not need to convince me further or justify dig. I only suggested q as an alternative if another tool had to be bundled to replace it, but as I had shown previously anyone can bring their own q / doggo or similar self-contained utility very easily into the container without it being in the actual image.

Having a debug image is not useful since user cannot switch images in production when they need to debug. The debugging has to happen in the same container since it has to use the same source IP to be able to replicate the issue.

There are ways around that too, paying customers of Docker can also use their debug feature AFAIK as another option.

I have helped many users troubleshoot problems without this concern you've stated, often it turns out to be a misconfiguration on their end, but sometimes it's more niche environment conditions.

Generally if I can provide a complete self-isolated minimal reproduction that works, they can verify that in their environment, and we can go from there. Those solutions then become part of docs / examples / FAQ.

As stated though, I fully understand the preference and choice to keep it simple. docker-mailserver isn't really any better (it's a big image in comparison and includes dig too IIRC).

libmsquic

With the setup you propose, I am confident that it would break within a couple of months. The library devs can change install paths, or rename the files, have different dependencies, or have a dependency of some other lib with specific version. Its unpredictable what may happen.

And I am confident that in such an event resolving that is likely trivial.

As stated, in the event I am horrifically mistaken, the quick fix for you would be a couple of lines to change in the Dockerfile, which I've demonstrated above already. No need to investigate and think about it if it worries you in that situation, that will immediately revert you back to the same Dockerfile that you feel comfortable with.

In such case, I have to downgrade to earlier version, built the image, and test it manually again to see if QUIC works before releasing the image.

  1. You should be able to automate such a test if you've had to do that a few times.
  2. That should effectively be an ARG for version pinning if you need it, roll back becomes very simple then.
  3. With the approach I provided, there should be no build overhead concerns. You already have the project built externally, if you instead had a dockerized build environment you can have that as a separate stage that is cached accordingly.

Community

While this project is OSS, its deployed to several paying clients at my full time job too. There are also some users paying for support.

No worries then :)

I try to answer all support queries as the project is not yet popular and lacks documentation. I am hoping that the community gets bigger over time, here and on reddit, so that will spend less time with queries.

I had hoped the same, but over 3 years with docker-mailserver which is now over 14k stars, it was at 6k when I joined as a maintainer in early 2021. So I can relate, and only hope you have a better experience with attracting contributors 😄

ShreyasZare commented 1 month ago

I should probably bow out of the discussion as I think we are both too polite and feel a need to respond fully? 😆

lol

Will check out the PR soon.