Blog

BIND as a private network DNS Server deployed with Docker and Ansible

Iveta Paneva
Iveta Paneva
DevOps and Cloud Engineer
13.04.2023
Reading time: 11 mins.
Last Updated: 12.02.2024

Table of Contents

As the demand for the IT industry is globally expanding, open-source software becomes an ever-growing part of everyday practices today.

As engineers, we are well aware that we are always searching for software solutions to improve our work, to reduce time, concomitantly to be well secured and productive.

Therefore we will emphasize flexible and customizable software such as the DNS service – BIND9, automation tool – as the popular Ansible software and platform as a service with minimal usage of resources like Docker container. And now we will explore how to optimize our day-to-day work using those key principles to decrease the execution flow of future tasks.

We will guide you through the process of setting up a Docker container for BIND9, configuring it, and using Ansible to deploy and manage the server. By the end of this article, you will have a better understanding of how to deploy a DNS server using BIND9, Docker, and Ansible, and how to customize it to meet your organization’s specific needs.

Benefits of using BIND9 on Docker with an Ansible role

We will look up several reasons why using BIND9 on Docker with an Ansible role can be beneficial for managing DNS services. A few of these advantages are below.

  • Portability: Docker offers a lightweight and portable containerization platform that permits us to run BIND9 on any server that supports Docker, without regard to the underlying operating system. This feature makes it easy to move DNS services across different environments, such as development, testing, and production.
  • Scalability: With Docker, you can scale your BIND9 infrastructure up or down easily by adding or removing containers. For instance, this can help you to handle fluctuations in DNS traffic.
  • Consistency:  The Ansible tool ensures a consistent configuration of BIND9 across multiple servers. By using Ansible roles, you can define the entire configuration of BIND9 as code and troubleshoot DNS servers. It guarantees consistency across the entire infrastructure.
  • Automation: Ansible enables you to automate the deployment and configuration of BIND9 across numerous servers, which reduces the possibility of configuration errors while saving time and effort.
  • Versioning: By describing the entire configuration of BIND9 as code, you can version control your DNS infrastructure. This makes it easier to track changes and revert to previous configurations.

Focus on being productive instead of busy

Introduction to BIND9: Setting up a DNS Server 

Firstly, we will define BIND9, show you the basic configuration files that we will use, and explain how to create the BIND9 network. BIND9, short for Berkeley Internet Name Domain version 9, is software that provides DNS (Domain Name System) services. DNS can be described as a phone book for the internet, translating human-readable domain names such as itgix.com into IP addresses that computers can understand, such as 208.65.153.238. BIND9 is responsible for managing and resolving these domain names, enabling computers and other devices to access websites and online services by translating the domain names into their corresponding IP addresses.

And now let’s see the configuration files:

The main components of Bind9 that we will be using are:

  • named.conf – is the main configuration file for named (the primary main daemon/service that provides DNS name resolution). It specifies the global options for named and defines the zones that named is authoritative for.

Our named.conf file – example:

zone "{{ domain.internal }}" {
             type master;
             file "/etc/bind/db.itgix";

Where “domain.internal” is a variable which can be placed in /etc/ansible/group_vars/bind-vars.yml and Ansible will read the value from this file.

The content of the /etc/ansible/group_vars/bind-vars.yml file is:

domain:
    internal: "stage.itgix"

To make it easier for us, we are using variables, which allow us to avoid editing the configuration files of BIND9 every time. We create a separate file where our variables are defined. So whenever we want to add or remove it will be less time and effort.

Next, we will look at the named.conf.options file:

  • Named.conf.options –  it is a configuration file in BIND9 that sets global options for the DNS server. It stores parameters such as the server’s listening IP addresses and the port number on which the server listens for DNS queries.

Our example of named.conf.options file is:

options {
        directory "/var/cache/bind";
        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable 
        // nameservers, you probably want to use them as forwarders.  
        // Uncomment the following block, and insert the addresses replacing 
        // the all-0's placeholder.

        // forwarders {
              8.8.8.8;
        // };      //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys       //========================================================================
        dnssec-validation false;
        #listen-on-v6 { any; };
};

The “dnssec-validation” option is set to false. It means we would not use these extensions. DNSSEC stands for Domain Name System Security Extensions. It is a set of security extension specifications/protocols and standards that provide a secure mechanism for resolving domain names to IP addresses. 

The “forwarders” are used for allowing us to resolve domains outside of the defined zone.

The “listen-on-v6” option is used to specify vers.6 IP addresses that the BIND9 server should listen on for incoming DNS queries. In this file, we have commented on the line “listen-on-v6” as we would not use such an IP address version.

The next file is:

  • Zone files: – The zone/db file contains the mapping between domain names and IP addresses or other resource records. They are used to define the DNS hierarchy and provide name resolutions for specific domains.

These files are named according to the domain name and they typically have extensions such as .db or .zone. Some of the types of this file are db.local for the localhost zone, db.root for the root zone, and db.example.com for the example.com domain. The db files are included in the named.conf configuration file using the zone statement.

Our file is “db.itgix”:

$TTL    604800
@	IN      SOA    ns.{{ domain.internal }}. {{ domain.internal }}. (
                     2022080501         ; Serial
                           7200         ; Refresh
                           3600         ; Retry
                         604800         ; Expire
                           7200 )       ; Negative Cache TTL; 
@       IN      NS    ns.{{ domain.internal }}.
ns 	IN 	A	{{ ansible_ssh_host }}

{% for record in records %}
{{ record.subname }}  IN  {{ record.type }}  {{ record.dest }}
{% endfor %}

Since we are using variables in this file too, we added the following entries in the  /etc/ansible/group_vars/bind-vars.yml file.

records:
  - { subname: "jenkins", type: "A", dest: "IPv4" } - place your desired IP in the quotes
  - { subname: "prometheus", type: "A", dest: "IPv4" } - place your desired IP address in the quotes

In summary, named.conf.options is a global configuration file that sets options for the BIND9 server, while db files are used to store zone data for specific domains.

Configuring the Docker container to use BIND9

As we saw how to set DNS records and we configured the BIND9 main configuration files, now we can proceed with the next step – Configuring the docker container to use BIND9.

To start, let’s see what a Docker container is:

Docker is a software technology that allows developers to create, deploy, and run applications in a containerized environment. A container is a standalone executable package that includes everything an application needs to run, including code, libraries, and system tools.

Think of a container as a small virtual machine that only includes the necessary resources to run a specific application. This means that multiple containers can run on a single host machine without interfering with each other, providing greater efficiency and flexibility for developers.

Let’s start creating the Dockerfile.

A Dockerfile is a text file that contains a set of commands for building a Docker image. Which file is used in the Docker build process to create a container image that can be used to run an application or service?

We need to create Dockerfile which will install our Bind9 DNS service in a container: 

FROM ubuntu/bind9:9.16-20.04_edge
RUN mkdir /var/cache/bind -p
RUN chown bind:bind /var/cache/bind
RUN chmod 775 /var/cache/bind
RUN rm -rf /etc/bind/*
COPY named.conf /etc/bind/named.conf
COPY db.itgix /etc/bind/db.itgix
COPY named.conf.options /etc/bind/named.conf.options
RUN ls -l /etc/bind/
EXPOSE 53

Explaining the above file configuration:

FROM ubuntu/bind9:9.16-20.04_edge:Specifies the base image we use for the build process. We use Ubuntu image with BIND9 installed.

RUN mkdir /var/cache/bind -p: Creates a directory called /var/cache/bind with the -p flag, which creates any necessary parent directories.

RUN chown bind:bind /var/cache/bind: We have to set bind for owner and group, it is necessary because BIND9 runs as the bind user.

RUN chmod 775 /var/cache/bind: Here we set the permissions of the /var/cache/bind directory to 775 (rwx-rwx-r-x), which allows the bind user to read, write, and execute files in the directory.

RUN rm -rf /etc/bind/*: With this step, we will remove the files from  /etc/bind/ because the next commands will copy our custom files into this directory, from where BIND will read.

The below commands are copying the files to /etc/bind directory in the container:

COPY named.conf /etc/bind/named.conf: 

COPY db.itgix /etc/bind/db.itgix

COPY named.conf.options /etc/bind/named.conf.options

RUN ls -l /etc/bind/: Here we list the copied files in /etc/bind in the container, to verify that they were copied successfully.

EXPOSE 53: This command exposes port 53, which is the default port used by the BIND9 DNS server.

As we can see, the file is a very basic setup and user-friendly, using a custom configuration. 

Creating an Ansible role for BIND9

And now we are ready to proceed with our final step – Creating an Ansible role for BIND9.

Firstly let’s explain what Ansible is and define the modules that we are using in our role.

Ansible is a user-friendly tool for automating and managing IT infrastructure. It allows us to easily manage multiple servers and applications through a simple, easy-to-understand language without advanced programming knowledge. With its simplicity and ease of use of language, it helps us to save time and increase productivity.

The Ansible roles contain a few tasks: 

We created a file in /etc/ansible/playbooks/bind9.yml, where we set instructions of which role to be executed and on which host, as the following example:

---
- hosts: localhost
  roles:
- { role: "bind9" }

And now let’s see the whole role and then we will describe what the modules are used for.

The role is created in /etc/ansible/roles/bind9/tasks/bind9-container.yml

---
- name: Creates directory
  ansible.builtin.file:
    path: /opt/bind9
    state: directory
    owner: root
    group: root
    mode: 0775
  become: true
  tags:
    - bind_deploy

- name: Copy Dockerfile, named.conf, named.conf.options, db.itgix to container.
  template:
    src: "{{ item }}"
    dest: /opt/bind9
    owner: root
    group: root
    mode: 0775
  with_items:
    - named.conf
    - Dockerfile
    - named.conf.options
    - db.itgix
  become: true
  tags:
    - bind_deploy

- name: Create a network
  docker_network:
    name: "{{ domain.internal }}"
    ipam_options:
      subnet: 172.24.0.0/24
      iprange: 172.24.0.0/24
    state: present
  become: true
  tags:
    - bind_deploy

- name: Build the image
  docker_image: 
    name: bind9
    tag: "{{ build_tag }}"
    path: /opt/bind9
    source: build
  become: true
  tags:
    - bind_deploy

- name: Running the container
  docker_container:
    name: bind9
    image: bind9:{{ build_tag }}
    state: started
    restart: true
    exposed_ports:
      - 53:53/tcp
      - 53:53/udp
    networks:
      - name: "{{ domain.internal }}"
    recreate: true
  become: true
  tags:
    - bind_deploy

We will get you to introduce what modules are used for, as well as the syntax of the Ansible role. 

In Ansible, modules are used to perform specific tasks on managed nodes, that are executed on remote hosts to automate the desired task, for instance – installing a package, starting a service, or configuring a network interface.

Although modules can be written in any programming language, the most common language for writing Ansible modules is Python. Ansible ships with a large number of built-in modules, and there are also many third-party modules available that can be downloaded and used.

The modules inside of the Ansible role we used are:

  • ansible.builtin.file: This module is used to create a directory at the specified path with the specified owner, group, and mode.
  • template: This module is used to copy the files named.conf, Dockerfile, named.conf.options, and db.itgix to the /opt/bind9 directory. The source files are templates that are customized with variables specific to the target environment.
  • docker_network: This module is used to create a new Docker network named “{{ domain.internal }}” with the IP address range 172.24.0.0/24.
  • docker_image: This module is used to build a Docker image named “bind9” with the specified build tag, using the Dockerfile located at /opt/bind9.
  • docker_container: This module is used to run a Docker container named “bind9” with the specified image and restart policy, exposing TCP and UDP ports 53, and attaching it to the “{{ domain.internal }}” Docker network. If the container already exists, it will be recreated.

What does the Ansible role do?

The first task creates a directory “/opt/bind9”, where the BIND9 would be installed. We are giving 0755 (drwx-r-x-r-x) permissions of the directory for group root and owner root.

The second task uses the template module to copy our four files created by our own files to the container in “/opt/bind” so when we install BIND9 it will read from those files.

The third part is to create a network to be able to operate the DNS container with static IP addresses. The following command creates a network using variable {{ domain.internal }},  which is written in /etc/ansible/group_vars/bind-vars.yml with range 172.24.0.0/16.

The fourth task builds a Docker image named bind9:{{ build_tag }} from the /opt/bind9 directory.

Finally, the fifth task runs a Docker container named bind9 from the bind9:{{ build_tag }} image with the “recreate” flag set to true so the container can be recreated always, and “exposed_ports” set to 53 to expose the DNS port to the outside world. It also connects the container to the {{ domain.internal }} network.

For all of the tasks you will see a tag that we are using:

tags:
- bind_deploy

Tags: if you put tags on chosen modules, and then execute the Ansible role calling this tag, this will be applicable only for the modules where the tag exists.

Run the playbook:

ansible-playbook bind9.yml --tags bind_deploy

As a final conclusion using BIND9 on Docker with Ansible role offers us numerous advantages for managing DNS servers. This combination provides a flexible and automated way to manage DNS services, reduce configuration errors, and ensure consistency across the entire infrastructure. Moreover, the ability to define and version the entire configuration of BIND9 as code through Ansible roles, makes it easier to maintain and troubleshoot DNS servers, while also allowing rapid deployment and rollback of changes. Overall, using BIND9 on Docker with Ansible role can provide a flexible, automated, and consistent way to manage DNS services, while also enabling portability, scalability, and versioning of the entire infrastructure.

Conclusion

To sum up, using BIND9 on Docker with Ansible role is a powerful and efficient way to manage DNS services in modern IT environments.

We appreciate you being a part of this article and hope you found it helpful. Thank you for investing your time.

If you need help with using BIND9 on Docker with an Ansible role and unlocking the advantages of managing DNS servers, you can contact our expert team of seasoned DevOps engineers.

More Posts

In the dynamic world of serverless computing, securing your AWS Lambda function is crucial. However, one often neglected area is the security of containerized applications in Amazon Elastic Container Registry...
Reading
Note: The following example demonstrates upgrading a Kubernetes cluster from version 1.23 to 1.24. Replace the version numbers according to your specific setup. To ensure a seamless upgrade, it’s crucial...
Reading
Get In Touch
ITGix provides you with expert consultancy and tailored DevOps services to accelerate your business growth.
Newsletter for
Tech Experts
Join 12,000+ business leaders and engineers who receive blogs, e-Books, and case studies on emerging technology.