Freeing up port 53

I’m trying to build a custom DNS server using Balena, and I think I got stuck at a point. I searched around for this issue but couldn’t come to a conclusion so I want to see what the community has experienced in this topic.

When I try to bind port 53 in my docker-compose.yml file, the service fails to start because dnsmasq is already running on the host. Is there a way to disable dnsmasq and what would the consequences be in that case? (when I bind it to a different port, it can listen but not respond via UDP, but I think that’s a different issue)

Another container that runs on the device is a web service and that starts and listens fine, I can query it and get a response as usual.

Hi, can you please share your docker-compose and Dockerfile a well as the version of balenaOS? The OS does have dnsmasq running on port 53, but it’s binding to 127.0.0.2, so it shouldn’t get in the way.

Sure I’m using balenaOS 2.38.0+rev1 for qemu machines and balenaOS 2.48.0+rev1 for raspberry pi, perhaps I shouldn’t use a network for this? I did it that way because the DNS container needs to talk to a Redis instance but that shouldn’t be exposed to the LAN.

EDIT:
In the meantime, I found an application issue and solved the no response thing, now only port 53 remains.
I realised that I didn’t set privileged: true on the service, so please see these updated files:

docker-compose.yml
version: '2.1'

services:
  dns:
    build:
      context: .
      args:
        PACKAGE: 'dns'
        EXPOSE: '53 53/udp'
    ports:
      - '53'
      - '53/udp'
    privileged: true
    restart: always
    environment:
      PORT: '53'
      FLAG_IPV6: 'false'
    labels:
      io.balena.update.strategy: download-then-kill
    depends_on:
      - redis
    networks:
      - internal

  web:
    build:
      context: .
      args:
        PACKAGE: 'web'
        EXPOSE: '3000'
    ports:
      - '80:3000'
    privileged: true
    restart: always
    labels:
      io.balena.update.strategy: download-then-kill
    depends_on:
      - redis
    networks:
      - internal

  redis:
    build:
      context: ./packages/redis/service
    restart: always
    labels:
      io.balena.update.strategy: download-then-kill
    networks:
      - internal
    volumes:
      - redis-data:/data

volumes:
  redis-data:

networks:
  internal:
    driver: bridge
Dockerfile
FROM balenalib/%%BALENA_ARCH%%-alpine-node:12.18.3-3.12-20200915 as buildbase
FROM balenalib/%%BALENA_ARCH%%-alpine-node:12.18.3-3.12-run-20200915 as runbase

FROM buildbase as depskeleton

WORKDIR /app

# Install NodeJS and build dependencies
RUN apk add --no-cache \
  make gcc g++ linux-headers gnupg git zlib autoconf \
  automake zlib zlib-dev python2 python3 libpng-dev

RUN yarn global add lerna

ENV LIBRARY_PATH=/lib:/usr/lib

COPY ./package.json ./yarn.lock ./lerna.json ./
COPY ./patches/* ./patches/
COPY ./packages/<corp_package>/package.json ./packages/<corp_package>/
COPY ./packages/dns/package.json ./packages/dns/
COPY ./packages/web/package.json ./packages/web/

##################
## Production deps
##################

FROM depskeleton as proddeps

ENV PYTHON=/usr/bin/python2
ENV NODE_ENV=production

RUN lerna bootstrap

##################
## Development deps
##################

FROM proddeps as devdeps

ENV NODE_ENV=development

RUN lerna bootstrap

##################
## Source code
##################

FROM devdeps as sources

# Tell the builder to build an optimised version of the bundle
ENV PATH="${PATH}:./node_modules/.bin" \
  # Set the max memory higher, it's 1.5G by default. This option specifies how
  # much memory the program can use BEFORE garbage collection. This should be
  # less than the available system memory.
  NODE_OPTIONS=--max_old_space_size=2048

# Copy the whole source code into the container
COPY . .

##################
## Build
##################

FROM sources as build
ARG PACKAGE

WORKDIR /app

RUN yarn lerna run build \
  --scope=@<corp_org>/${PACKAGE} \
  --stream \
  --include-filtered-dependencies \
  --concurrency=1

##################
## Runtime
##################

FROM runbase as runtime
ARG PACKAGE
ARG EXPOSE

WORKDIR /app

COPY --from=proddeps /app .
COPY --from=build /app/packages/${PACKAGE}/build .

EXPOSE ${EXPOSE}

ENTRYPOINT [ "node", "/app/index.js" ]

Hi there,

The best example of running a DNS server on balena is of course the pi-hole project, available here: https://github.com/klutchell/balena-pihole & https://www.balena.io/blog/deploy-network-wide-ad-blocking-with-pi-hole-and-a-raspberry-pi/. Hopefully this example will help you.

Looking more deeply at that example, in the pi-hole Dockerfile there are steps taken to work around an overly-aggressive dnsmasq bind: https://github.com/klutchell/balena-pihole/blob/281683fb302eb0dacb23033f8d3fec8e7e9caf05/pihole/Dockerfile.rpi#L25-L28. Perhaps you can run a dnsmasq in your container that forwards requests to your service, and ensure it’s bound on the interface you’re after?

I hope that helps!