You want to make simple services, that do things. To be able to modify that role out, at code base, at will? Ok, probably interested in Docker or things like it (containers). In this post we will be exploring some of my findings and this will sort of be a notes/detail reference for myself in the future. Thanks me.

Docker is a great container environment

Docker Build and Provisioning

Now, there must be a couples files that have been crafted governing the provisioning, and possible creation of your Docker container. This guide assumes you have an average distro and have installed the latest docker environment.

RedHat Enterprise Linux – docker

Ubuntu – docker

As I am getting to understand it, these are:

docker build -t containername .

In this example you use the “Dockerfile” file where you put the actual code the builds your docker container, can be as simple as getting an image, or, can be as complicated as layers and all the rest

docker-compose up -d

When you do this, it uses the “docker-compose.yml” file to provision the Docker container to your host

In most default configurations when you provision some default Docker container image it will network via a Bridge network connection. You will publish port forwards from your host to the docker0 network (in most cases). Some environments alter the default rollout for a standard environment, and to this, Docker can support quite a wide array. You just might need to know how to delivery that arrangement.

Standard deployment

In most cases you would be following a standard deployment suggestion from Docker Hub or someone’s Github project, you probably where told do a ‘docker run -d groupname/containername:version’ and this uses Docker’s bridge and port forward usually. This is the “standard” deployment model.

Here’s a much more filled out example, one that could exist as a script in of itself. This builds a PiHole using an IP that would be assigned on the primary Ethernet controller for the host it is ran on:

docker container stop pihole01
docker container rm pihole01
docker volume rm pihole_data
docker volume create pihole_data
docker volume rm pihole_dnsmasq
docker volume create pihole_dnsmasq
docker pull pihole/pihole:latest
docker run -d \
    --name pihole01 \
    -p -p \
    -p \
    -p \
    -p [::]:53:53/tcp -p [::]:53:53/udp \
    -p [::]:80:80 \
    -p [::]:443:443 \
    --restart=always \
    -e TZ="America/Chicago" \
    -v pihole_data:/etc/pihole/ \
    -v pihole_dnsmasq:/etc/dnsmasq.d/ \
    --dns= --dns= \
    --restart=unless-stopped \
    --hostname pihole01 \
    -e WEBPASSWORD="random" \
    -e VIRTUAL_HOST="pihole01" \
    -e PROXY_LOCATION="pihole01" \
    -e ServerIP="" \

If you were going admin the PiHole, you would browse to the “” address and enter your password of “random” to login. When a host resolves a DNS address, it would connect to the “” IP but, the host running that PiHole Docker Container (whatever IP it might be) would actually reply back to the requester. It can look odd on the network if you were expecting something else to happen.

If you are after a more “local network connection” or standard “service style” arrangement, then you would be interested in Docker’s other features.

You will need to make a new network for this via “docker” commands, you can also do this in a “docker-compose” method too, I will try to start with the first and detail the second. Haven’t actually used the second method yet, so…

Custom Deployment – Bind configurable via Webmin

In this deployment we will be using “docker-compose” and this must be installed and of course as it relies on Python, you will have to have and a series of dependencies installed. Please return once you have this installed, you should not have to do much configuration on a standard flavor of Linux as far as I know.

The first step is creating the new network for the IP resolved Docker Containers

Issue these commands to create your network interface you will deploy against:

docker network create dockerext01 --driver=macvlan --subnet= --gateway= --ip-range= -o parent=eth0

sudo ip link set eth0 promisc on

Now you can use the Dockerfile and docker-compose.yml files plus a likely file to configure to this environment. At the end we cover how to execute the whole group of files. The bind example is a good strong example in my mind because the original creator built out the as such. It helped me learn some of the rest of the instrumentation more easily. Thank you!

Docker Compose is a nice Python utility to deploy container assets

A bind configuration (modified from sameersbn/docker-bind):

Dockerfile (copied and modified from source)

FROM ubuntu:focal AS add-apt-repositories

RUN DEBIAN_FRONTEND=noninteractive apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y apt-utils \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates wget \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y gnupg \
 && wget -q -O- | apt-key add \
 && echo "deb sarge contrib" >> /etc/apt/sources.list

FROM ubuntu:focal

    BIND_VERSION=9.16.1 \
    WEBMIN_VERSION=1.984 \
    DATA_DIR=/data \

LABEL maintainer="email@domain.tld"

COPY --from=add-apt-repositories /etc/apt/trusted.gpg /etc/apt/trusted.gpg

COPY --from=add-apt-repositories /etc/apt/sources.list /etc/apt/sources.list

RUN rm -rf /etc/apt/apt.conf.d/docker-gzip-indexes \
 && DEBIAN_FRONTEND=noninteractive apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y \
      bind9=1:${BIND_VERSION}* \
      bind9-host=1:${BIND_VERSION}* \
      dnsutils \
      webmin=${WEBMIN_VERSION}* \
 && rm -rf /var/lib/apt/lists/*

COPY /sbin/

RUN chmod 755 /sbin/

EXPOSE 53/udp 53/tcp 10000/tcp

ENTRYPOINT ["/sbin/"]

HEALTHCHECK --interval=12s --timeout=12s --start-period=30s \
  CMD [ "/usr/bin/dig", "+short", "+norecurse", "+retry=0", "@", "ns02.homelab.home", "||", "exit 1" ]

CMD ["/usr/sbin/named"]

docker-compose.yml (created by me, there is a reference but it was quite minimal)

version: "3"

    container_name: ns02
    image: bind_homelab:latest
      context: .
      dockerfile: Dockerfile
    hostname: ns02
    domainname: homelab.home
      - "ns02:"
         - ns02
      - "53:53/tcp"
      - "53:53/udp"
      - "10000:10000/tcp"
      - bind:/data/bind
      - webmin:/data/webmin
    restart: always

  dockerext02:   # externally created network (later in article)
    external: true

  webmin: (modified from source, only slightly)

set -e

# usage: file_env VAR [DEFAULT]
#    ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
#  "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env() {
  local var="$1"
  local fileVar="${var}_FILE"
  local def="${2:-}"
  if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
    echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
    exit 1
  local val="$def"
  if [ "${!var:-}" ]; then
  elif [ "${!fileVar:-}" ]; then
    val="$(< "${!fileVar}")"
  export "$var"="$val"
  unset "$fileVar"

file_env 'ROOT_PASSWORD'



create_bind_data_dir() {
  mkdir -p ${BIND_DATA_DIR}

  # populate default bind configuration if it does not exist
  if [ ! -d ${BIND_DATA_DIR}/etc ]; then
    mv /etc/bind ${BIND_DATA_DIR}/etc
  rm -rf /etc/bind
  ln -sf ${BIND_DATA_DIR}/etc /etc/bind
  chmod -R 0775 ${BIND_DATA_DIR}

  if [ ! -d ${BIND_DATA_DIR}/lib ]; then
    mkdir -p ${BIND_DATA_DIR}/lib
    chown ${BIND_USER}:${BIND_USER} ${BIND_DATA_DIR}/lib
  rm -rf /var/lib/bind
  ln -sf ${BIND_DATA_DIR}/lib /var/lib/bind

create_webmin_data_dir() {
  mkdir -p ${WEBMIN_DATA_DIR}
  chmod -R 0755 ${WEBMIN_DATA_DIR}
  chown -R root:root ${WEBMIN_DATA_DIR}

  # populate the default webmin configuration if it does not exist
  if [ ! -d ${WEBMIN_DATA_DIR}/etc ]; then
    mv /etc/webmin ${WEBMIN_DATA_DIR}/etc
  rm -rf /etc/webmin
  ln -sf ${WEBMIN_DATA_DIR}/etc /etc/webmin

disable_webmin_ssl() {
  sed -i 's/ssl=1/ssl=0/g' /etc/webmin/miniserv.conf

set_webmin_redirect_port() {
  echo "redirect_port=$WEBMIN_INIT_REDIRECT_PORT" >> /etc/webmin/miniserv.conf

set_webmin_referers() {
  echo "referers=$WEBMIN_INIT_REFERERS" >> /etc/webmin/config

set_root_passwd() {
  echo "root:$ROOT_PASSWORD" | chpasswd

create_pid_dir() {
  mkdir -p /var/run/named
  chmod 0775 /var/run/named
  chown root:${BIND_USER} /var/run/named

create_bind_cache_dir() {
  mkdir -p /var/cache/bind
  chmod 0775 /var/cache/bind
  chown root:${BIND_USER} /var/cache/bind

first_init() {
  if [ ! -f /data/webmin/.initialized ]; then
    if [ "${WEBMIN_INIT_SSL_ENABLED}" == "false" ]; then
    if [ "${WEBMIN_INIT_REFERERS}" != "NONE" ]; then
    touch /data/webmin/.initialized


# allow arguments to be passed to named
if [[ ${1:0:1} = '-' ]]; then
  set --
elif [[ ${1} == named || ${1} == "$(command -v named)" ]]; then
  set --

# default behaviour is to launch named
if [[ -z ${1} ]]; then
  if [ "${WEBMIN_ENABLED}" == "true" ]; then
    echo "Starting webmin..."
    /etc/init.d/webmin start

  echo "Starting named..."
  exec "$(command -v named)" -u ${BIND_USER} -g ${EXTRA_ARGS}
  exec "$@"

Extra, a script to call the build process (also mine, but incredibly basic, and more to understand additional flags)

docker-compose up -d --force-recreate --always-recreate-deps --build --remove-orphans

After you create all the files in a directory together you would then issue these commands:

chmod +x *.sh

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.