Skip to content

alpine-pdnsd

Deprecation Warning

This image has been deprecated and no updates (or support) may be available in future. Even though it is a container, it may or may not keep working as expected, use at your own risk.

updated stars forks watchers issues issues-pr
pulls stars armhf x86_64

Container for Alpine Linux + S6 + Proxy DNS Daemon


This image containerizes the PDNSd DNS server with persistent caching-to-disk and recursion/forwarding, mainly used to resolve domain names (both local devices and outsiders) and blocking ads inside the local network.

Based on Alpine Linux from the s6 image with the compiled pdnsd binaries installed in it.


Get the Image

Pull the image from Docker Hub.

docker pull woahbase/alpine-pdnsd
Image Tags

The image is tagged respectively for the following architectures,

   armhf

   x86_64

latest tag is retagged from x86_64, so pulling without any tag fetches you that image. For any other architectures specify the tag for that architecture. e.g. for armv8 or aarch64 host it is alpine-pdnsd:aarch64.

non-x86_64 builds have embedded binfmt_misc support and contain the qemu-user-static binary that allows for running it also inside an x86_64 environment that has support for it.


Run

Running the container starts the service.

docker run --rm -it \
  --name docker_pdnsd \
  -p 53:53/tcp \
  -p 53:53/udp \
  -v $PWD/data:/data \
  -v /etc/hosts:/etc/hosts:ro \
woahbase/alpine-pdnsd:x86_64
Multi-Arch Support

If you want to run images for other architectures on a x86_64 machine, you will need to have binfmt support configured for your machine before running the image. multiarch, has made it easy for us containing that into a docker container, just run

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

Now images built for other architectures will also be executable. This is optional though, without the above, you can still run the image that is made for your architecture.


Configuration


  • Configuration file is at /etc/pdnsd.conf, edit or remount this with your own. A sample is provided in /defaults, this gets copied when no such file exists before service is started. Put your custom config file at /data/pdnsd.conf and it will be used instead.

  • Local names are served from /data/hosts/hosts.local, if not found, /etc/hosts is copied.

  • A default blocklist from the following sources are provided as default at /etc/hosts.blocked. On start, this is copied over to /data/hosts/hosts.blocked, if not existing already. Replace this file to use your own blocking list. Current build combines the following lists ..

  • To unblock a specific domain from the blocklist, put it inside /data/hosts/hosts.whitelisted (needs restart). To manually unblock using sed..

    sed -i -e 's/\([ . ]\)rt.com/\1notrt.com/g' /data/hosts/*
    

  • On many systemd derivations e.g. Ubuntu, 53/udp may be already in-use by systemd-resolved. In that case, it will need to be stopped first before dns server is started, with,

    sudo systemctl stop systed-resolved
    

Stop the container with a timeout, (defaults to 2 seconds)

docker stop -t 2 docker_pdnsd

Restart the container with

docker restart docker_pdnsd

Removes the container, (always better to stop it first and -f only when needed most)

docker rm -f docker_pdnsd

Shell access

Get a shell inside a already running container,

docker exec -it docker_pdnsd /bin/bash

Optionally, login as a non-root user, (default is alpine)

docker exec -u alpine -it docker_pdnsd /bin/bash

Or set user/group id e.g 1000/100,

docker exec -u 1000:100 -it docker_pdnsd /bin/bash

Logs

To check logs of a running container in real time

docker logs -f docker_pdnsd

As-A-Service

Run the container as a service with the following as reference (and modify it as needed).

With docker-compose (alpine-pdnsd.yml)

services:
  pdnsd:
    container_name: pdnsd
    deploy:
      resources:
        limits:
          cpus: '1.00'
          memory: 384M
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 5
        window: 120s
    healthcheck:
      interval: 2m
      retries: 5
      start_period: 5m
      test:
        - CMD-SHELL
        - >
          pdnsd-ctl -c /data/pdnsd status
      timeout: 10s
    hostname: pdnsd
    image: woahbase/alpine-pdnsd:${PDNSD_TAG:-latest}
    network_mode: bridge
    ports:
      - protocol: tcp
        published: 53
        target: 53
      - protocol: udp
        published: 53
        target: 53
    volumes:
      - type: bind
        source: ${PDNSD_DIR:?err}/data
        target: /data/pdnsd
        bind:
          create_host_path: true
      # - type: bind
      #   source: ${PDNSD_DIR:?err}/config/pdnsd.conf
      #   target: /data/pdnsd.conf
      #   bind:
      #     create_host_path: false
      # - type: bind
      #   source: ${PDNSD_DIR:?err}/config/hosts.local
      #   target: /data/hosts/hosts.local
      #   bind:
      #     create_host_path: false
      # - type: bind
      #   source: ${PDNSD_DIR:?err}/config/hosts.blocked
      #   target: /data/hosts/hosts.blocked
      #   bind:
      #     create_host_path: false
      - type: bind
        source: /etc/localtime
        target: /etc/localtime
        read_only: true
        bind:
          create_host_path: false

As a SystemD service. (alpine-pdnsd.service)

[Unit]
Description=SystemD Service for PDNSd Server (dockerized)
Requires=multi-user.target network-online.target docker.service

After=network-online.target docker.service

# config dir must exist
ConditionPathIsDirectory=/services/pdnsd
# and should contain at least one file, pdnsd.conf
ConditionDirectoryNotEmpty=/services/pdnsd

[Service]
# resolved should be stopped before pdnsd starts to free up port 53
# ExecStartPre=/usr/bin/systemctl stop systemd-resolved.service

ExecStart=/usr/bin/docker run \
    --rm \
    --pull=never \
    -v /etc/localtime:/etc/localtime:ro \
    --hostname pdnsd \
    --name docker_%N \
    -p 53:53/tcp \
    -p 53:53/udp \
    -v /services/pdnsd:/data \
    -v /etc/hosts:/etc/hosts:ro \
    -c 256 \
    --memory 256mb \
    woahbase/alpine-pdnsd:x86_64

# ExecStop=/usr/bin/docker stop -t 2 docker_%N
# ExecStopPost=/usr/bin/docker rm -f docker_%N

# optional, re-start resolved after stopping pdnsd, if needed
# ExecStopPost=/usr/bin/systemctl start systemd-resolved.service

Restart=always
RestartSec=60
SyslogIdentifier=pdnsd

[Install]
WantedBy=default.target

Build Your Own

Feel free to clone (or fork) the repository and customize it for your own usage, build the image for yourself on your own systems, and optionally, push it to your own public (or private) repository.

Here's how...


Setting up


Before we clone the /repository, we must have Git, GNU make, and Docker (optionally, with buildx plugin for multi-platform images) setup on the machine. Also, for multi-platform annotations, we might require enabling experimental features of Docker.

Now, to get the code,

Clone the repository with,

git clone https://github.com/woahbase/alpine-pdnsd
cd alpine-pdnsd
Always Check Before You Make!

Did you know, we could check what any make target is going to execute before we actually run them, with

make -n <targetname> <optional args>

Build and Test


To create the image for your architecture, run the build and test target with

make build test 

For building an image that targets another architecture, it is required to specify the ARCH parameter when building. e.g.

make build test ARCH=armhf 
make build test ARCH=x86_64 

Make to Run


Running the image creates a container and either starts a service (for service images) or provides a shell (can be either a root-shell or usershell) to execute commands in, depending on the image. We can run the image with

make run 

But if we just need a root-shell in the container without any fance pre-tasks (e.g. for debug or to test something bespoke), we can run bash in the container with --entrypoint /bin/bash. This is wrapped in the makefile as

make shell 
Nothing vs All vs Run vs Shell

By default, if make is run without any arguments, it calls the target all. In our case this is usually mapped to the target run (which in turn may be mapped to shell).

There may be more such targets defined as per the usage of the image. Check the makefile for more information.


Push the Image


If the build and test steps finish without any error, and we want to use the image on other machines, it is the next step push the image we built to a container image repository (like /hub), for that, run the push target with

make push 

If the built image targets another architecture then it is required to specify the ARCH parameter when pushing. e.g.

make push ARCH=armhf 
make push ARCH=x86_64 

That's all folks! Happy containerizing!


Maintenance

Sources at Github. Images at Docker Hub.

Maintained (or sometimes a lack thereof?) by WOAHBase.