Build Your Own Kea Docker Image

May 30, 2024 · 19 mins read
Build Your Own Kea Docker Image

In the video below, we show you how to build your own Kea Docker image


Having a DHCP server on your network is extremely useful as it helps make it plug and play

In other words, you can connect a device to the network and chances are it can then access other devices

But in a small network, dedicating an entire computer to be just a DHCP server isn’t efficient

And using a firewall, for instance, as your DHCP server isn’t secure because it makes it vulnerable to more software bugs

One practical option for reducing compute resources is to run applications in containers

So in this video, we show you how to build your own Kea Docker image

Useful links:
https://hub.docker.com/search?q=&image_filter=official%2Cstore
https://kb.isc.org/docs/isc-kea-packages
https://kea.readthedocs.io/en/latest/arm/dhcp4-srv.html#dhcpv4-server-configuration
https://docs.docker.com/reference/cli/docker/network/create/
https://docs.docker.com/compose/compose-file/06-networks/
https://docs.docker.com/network/network-tutorial-macvlan/
https://docs.docker.com/compose/networking/

Assumptions:
Now because this video is specifically about building your own Docker image, I’m going to assume that you already have Docker installed as well as the build plugin, or you at least know how to install these

If not, then I do have another video available that shows you to set this up in a Debian virtual machine running on Proxmox VE for instance

Container File:
To make our own image, we’re going to take an existing image and add to it

Now what image you start with depends on what you’re trying to do, but it would be good to select images that are flagged as official and provided by a verified publisher

In our case, we just need a basic OS image

There are quite a few you can use, but we’ll be using the one from Debian

Now although you could create a container from this, connect to it and install the Kea DHCP server, any changes that are made to an instance are lost if it’s restarted for instance

So what we need to do is to create our own image that uses the Debian one as it’s base, with Kea installed

To create an image, we need to create a container file that defines what’s going on

For simplicity, it’s best to create separate folders for the images you want to build

In which case we’ll create a folder for Kea

mkdir -p images/kea

In other words, we’re creating a folder called images, and within there we’ll have sub-folders for each container image

Next we’ll switch to the folder for Kea

cd images/kea

For Docker you’ll probably want to create a file called Dockerfile, although if you use Podman you could call it Containerfile instead

nano Dockerfile
FROM debian:latest

RUN apt update && apt install curl gnupg apt-transport-https -y
RUN curl -1sLf 'https://dl.cloudsmith.io/public/isc/kea-2-4/setup.deb.sh' | bash
RUN apt update && apt install isc-kea-dhcp4-server -y

EXPOSE 67/udp

USER _kea:_kea

CMD ["/usr/sbin/kea-dhcp4","-c","/etc/kea/kea-dhcp4.conf"]

Now save and exit

The FROM instruction defines what we want to use as our base image, and in this case we want the latest version of the Debian image

The RUN instruction is to run commands as part of the build process, and in this case we’ll use it to install dependencies, run a script to setup the ISC repository and then install the IPv4 DHCP server software

At the time of recording, the latest version of the Debian script package is 2.6, which is too new

When I last checked, version 2.5 was reported as being a developmental branch

While that may now be production ready, I’ll be using 2.4 as I know this works

However, do check which version will be best for you https://cloudsmith.io/~isc/repos/

The EXPOSE instruction is to define which port(s) the container will listen on

A DHCP server listens on UDP port 67 so we’ll mention that

Now the goal of this container is to run a DHCP server, however, before we start it we change the default user to _kea and the group to _kea

Without this, the Kea DHCP server would be run by the root account and if there’s a vulnerability within the application there would then be the potential to install additional software for instance

NOTE: The default installation of Docker runs containers using the root account, so there is still the security risk of breaking out of the container with privileged access

Now, normally Kea would be run as a service in the background. But a container needs to be doing something all the time, otherwise, it will start up, complete its task and then stop

So in this case, we use the CMD instruction so that when the container starts it runs the DHCP server as an application in the foreground and so the container and thus the DHCP server, continues to keep running

To clarify, we use the RUN instruction to do things as part of the image build, in other words, our image will have the Kea DHCP server software installed

The CMD instruction on the other hand, is an instruction used when a container is created from the image. In this case, when the container starts, we want the DHCP server to be started in the foreground

NOTE: If a container file has multiple CMD instructions, only the last one will be used

Build Image:
The Dockerfile we’ve created contains instructions to build an image, so the next thing to do is to instruct Docker to do just that

We’ll use the build plugin by running a command like this

docker build -t kea_image:0.1 .

This will create an image which will be called kea_image using files in the current folder and it will be tagged as version 0.1

TIP: If you don’t specify a tag, this will be set to latest

In this case there’s only the Dockerfile itself in the folder, but if you’re creating your own application, the files for that could be stored in this folder and copied into the image

If you’re not familiar with version control, it helps to assign versions, not just for development reasons but also for users

So create an image with a version number, test it, and if it works create another version with a tag of latest

If you make any changes, create an image with a higher version number, test it, and if it works create an updated latest version

This makes sure users have easy access to the latest version, but they can also use older versions if for instance the newer version causes problems for them

You can get details of the Docker images you have available by running this command

docker images

This will give you details about the name, tag, the image ID, the date of creation and the size of each of the image files available

Docker Compose File:
Now that we have our own image for Kea we can run this in a container

To do that I’ll be using Docker Compose, but first I need to switch back to the main folder where the file for this is kept

cd

Next you need to either create or edit an existing Docker Compose file

nano docker-compose.yml

volumes:
  kea-data: {}

services:
  kea:
    image: kea_image:0.1
    container_name: kea
    ports:
      - '67:67/udp'
    restart: unless-stopped
    volumes:
      - ./kea/kea-dhcp4.conf:/etc/kea/kea-dhcp4.conf
      - kea-data:/var/lib/kea
    networks:
      infra:
        ipv4_address: 192.168.102.33

networks:
  infra:
    driver: macvlan
    driver_opts:
      parent: ens18
      com.docker.network.bridge.name: eth0
    ipam:
      config:
        - subnet: 192.168.102.0/24
          gateway: 192.168.102.254

Now save and exit

For a new file, you need to define volumes, services and networks sections

Otherwise you could just append the details for this container to the relevant sections

A DHCP server needs to keep track of IP leases, because if two devices have the same IP address it will cause a conflict

Because this is a container, we create a volume to store data in so that it can survive a restart of the container or host for instance

Currently we only have version 0.1 of our Kea image so we’ll use that when defining our container

And to make it easier to identify the container, we’ll give it a name of kea

We then define the port that this container will use and set this instance to be automatically restarted, unless it’s manually stopped for maintenance reasons for instance

Because changes within a container can be lost, we’ll map the configuration file to one on the host computer

In addition, we’ll map the folder where Kea stores the lease files to the volume that will be created

Normally Docker places containers into a separate network, but I’ve found this will cause a lot of problems for a DHCP server

In addition, the host won’t pass on broadcast traffic, and DHCP relies on this when the clients are in the same network

There are different ways to resolve this and unfortunately neither method I’ve found is ideal

One option is to use the host network driver. This removes the network isolation resulting in the container sharing the host’s networking namespace

But I’ve found this doesn’t work if Kea is being run with a non-root account. In other words, the host computer wasn’t listening for traffic on port 67

Running Kea with the root account does work, but then a software exploit could lead to an abilitity to run commands in the container with root privilege

What I’ve opted to do is to use the macvlan driver instead, but bear in mind this only works on Linux hosts and as far as I’m aware it isn’t available with Podman either

Either option is only supported in rooted mode, in other words, when a container is run using the root account, which defeats the purpose of using Podman anyway

To use macvlan mode, we define the network to use in the container details. I’ve called this one infra, but you can use whatever name you like

Then we specify a static IP address to use for this container

We then define the network in a networks section, and for me I’ve opted to call it infra

The default network driver is bridge, so for this network we set it to macvlan

We have to chose a host NIC to use for this, and for my computer it’s ens18

TIP: If you don’t know the network card name, use the command “ip a” and look for the name alongside the host’s IP address

As part of Kea’s configuration, we have to specify which interface(s) to listen on. We’ll need a static name for the container’s NIC, so we configure containers in this network to have an interface of eth0

Part of the network setup involves providing IPAM details, so we define the real network subnet, in other words, the one the host is in, along with the default gateway for the network

Configuration File:
As part of the container setup, we mapped a configuration file for Kea, so we need to create that on the host itself

First though, we’ll create a folder to store this in

mkdir kea

Then we’ll create a basic configuration file that covers the local subnet the container and host belong to

nano kea/kea-dhcp4.conf

{
"Dhcp4": {
    "interfaces-config": {
        "interfaces": [ "eth0" ],
        "dhcp-socket-type": "udp"
    },

    "lease-database": {
        "type": "memfile",
        "persist": true,
        "name": "/var/lib/kea/kea-leases4.csv",
        "lfc-interval": 3600
    },

    "renew-timer": 15840,
    "rebind-timer": 27720,
    "valid-lifetime": 31680,

    "option-data": [
        {
            "name": "domain-name-servers",
            "data": "192.168.102.30"
        },

        {
            "name": "domain-search",
            "data": "homelab.lan"
        }
    ],

    "subnet4": [
        {
            "subnet": "192.168.102.0/24",
            "pools": [ { "pool": "192.168.102.100 - 192.168.102.199" } ],
            "option-data": [
                {
                    "name": "routers",
                    "data": "192.168.102.254"
                }
            ]
            
            // Add reservations here
        },
        
        {
            "subnet": "192.168.250.0/24",
            "pools": [ { "pool": "192.168.250.100 - 192.168.250.199" } ],
            "option-data": [
                {
                    "name": "routers",
                    "data": "192.168.250.254"
                }
            ],
            
            "reservations": [
                // MAC address reservation
                {
                    "hw-address": "d1:6a:04:2d:44:2c",
                    "ip-address": "192.168.250.8",
                    "hostname": "testpc"
                }
             ]
        }
        
        // Add subnets here
    ]
}
}

Now save and exit

NOTE: The file is stored in JSON format and must be formatted correctly. For instance, parameters must be separated by a comma, except for the last one. Bear this in mind, because although ordering may not matter, if you decide to move parameters around you will likely have to delete and add commas where necessary

TIP: A text editor like Visual Studio Code will make this much easier

The container only has one interface, so we define eth0 as the interface to listen on, because that’s what we’ve specified in the container setup

While using the macvlan driver, I’ve still had problems with broadcast traffic not reaching the DHCP server. In which case, we change the socket type from RAW to UDP

TIP: This is also a security recommendation from the developer because a typical firewall can restrict direct UDP traffic but not RAW traffic

This does require additional work though, as we’ll discuss later

The server needs to keep a record of IP addresses that it leases to avoid duplication, particularly if the server or service restarts

It’s possible for Kea to use an external database to store leases in but in this example we’ll store them in a file that is created during the installation. This is intended to be a small network after all, so performance won’t be impacted

Although we’re using some default settings, these are a useful reference and putting them in makes it easier to change at later date if needed

For instance, by default a cleanup of the database will be performed every 3600 seconds or 1 hour to remove redundant information

NOTE: Even though we’ve only defined one CSV file, Kea will create multiple files and rotate them, hence why the folder was mapped and not just the database file

Next are timers, which I’ve changed so that a lease will last longer than 8 hours, enough for a typical office day

Global options have been defined for a DNS server as well as the domain name search which all subnets will typically use. This saves having to repeat the same information for each individual subnet

As well as defining the available IP addresses available in a pool, DHCPv4 options can also be defined that are specific to the pool, for example, the default gateway

Options defined here can override the global options, for instance, you could define a different DNS server to the global one

This is just a basic example for one IPv4 subnet, so do check out the documentation for more options
https://kea.readthedocs.io/en/latest/arm/dhcp4-srv.html#dhcpv4-server-configuration

DHCP Relay Agent:
Ideally, a DHCP server will be one of several infrastructure servers that are inside an isolated network, with access to them restricted by a firewall

And these infrastructure servers would have static IP addresses assigned to them

Other networks would then have a DHCP relay agent configured to listen out for DHCP broadcast traffic

However, that may not be the case in a SOHO network, with DHCP clients in the same network as the DHCP server

Because the DHCP server in the container isn’t receiving DHCP broadcast traffic and we’ve also configured Kea to only listen to UDP packets, you would therefore need to setup a DHCP relay agent within the server’s network

How you do that depends on what the network gateway is because typically that’s what the relay is configured on

In this lab example, I’m using OPNsense so we’ll configure that

First, navigate to Services | DHCPv4 and then click on Relay

NOTE: You can’t run a DHCP server and a DHCP relay service on OPNsense. So you can’t have OPNsense be the DHCP server for one network whilst it acts as a relay for another

You’ll need to enable the service and from the drop down menu select which interfaces the DHCP relay will listen on

Next you need to entire the IP address of the DHCP server

TIP: If you there is more than one server, you can separate them with a comma

Then click Save for the changes to take effect

Bear in mind, this is a relatively simple DHCP relay agent as it will send all DHCP traffic to both servers

So if you have defined more than one server, you have to make sure each one is configured to support only the necessary networks

Otherwise there is a risk of the same IP address being leased out to multiple computers, leading to a conflict where neither computer can operate on the network

Initial Testing and Troubelshooting:
Now we’ll start a container and test if our DHCP server can actually lease IP addresses

Since we’ve used Docker compose, we’ll run this command to start up the container

docker compose up -d kea

The first time this is run, the volume and network will also be created

We’ll first check the container is working, but we can use the -n parameter to show only recent ones that were created, in this case only the last one

docker ps -n 1

If the container keeps restarting then chances are there’s an error in the container or configuration file

You can check the container log for more information

docker container logs kea

If a fix needs applying, the container should be stopped first

docker compose stop kea

Any corrections should then be made and the container started again

docker compose up -d kea

Assuming the container looks to be working, it’s a matter of testing a DHCP client

Checking Leasing:
There may be times when you want to check IP leases, which in this setup are stored in a CSV file

While you can view files in the container volume, it seems easier to just connect to the container

To do that we’ll run this command

docker exec -it kea bash

In other words, we want an interactive terminal that gives us a bash shell

You can then check what files are in the folder

ls -l /var/lib/kea

Then use cat to check the contents, for example

cat /var/lib/kea/kea-leases4.csv

TIP: Using cat makes sense because there are few applications available to use in this Debian image

As mentioned earlier though, Kea will create multiple files, so you might need to check others

Now while /var/lib/kea/kea-leases4.csv will be the most recent file, you may not find what you’re looking for there

In which case, you might need to check /var/lib/kea/kea-leases4.csv.2 for example

TIP: Although files will probably show duplicate entries for the same IP lease, this is to be expected. It’s also why a regular cleanup is run to remove redundant entries from the database

Latest Version:
Now although the DHCP server seems to be working as expected, most users want to run the latest version, so we’ll create one

First we’ll switch to the folder our Dockerfile is in

cd images/kea

Then we’ll build another image, but we’ll omit the version in the tag

docker build -t kea_image .

As before, if you don’t specify a version, it will be labelled as being the latest version

Next we’ll switch back to the home folder

cd 

Then we’ll edit the docker compose file

nano docker-compose.yml

And update the image line

    image: kea_image:latest

Now save and exit

We’ll stop the DHCP server

docker compose stop kea

And then we’ll start a new container instance which will now use the latest image version

docker compose up -d kea

Nothing has really changed, so this one should work as before, but we’ll check all the same but using the -l option to to show only the last one

docker ps -l

Now since we’ve been making a lot of changes involving images and containers, we’ll clean up the system before we finish so that we can salvage some disk space

docker system prune -f

Configuration Changes:
When changes need to be made to the DHCP server configuration, then the configuration file on the host should be updated

nano kea/kea-dhcp4.conf

The changes don’t take affect immediately, and normally it would require a configuration reload or a service restart on the server

In this case, we’re running Kea as an application in a container so we’ll restart the container

docker compose restart kea

Summary:
In a small network, reducing your compute requirements will probably be important, because this should help reduce the power load, leading to less energy consumption and reduced costs

And, as we’ve shown, you can run infrastructure services like a DHCP server in containers to help you do that

Building your own images is relatively straightforward, once you understand the process

And while a DHCP server container does have some quirks, you can still create one

Sharing is caring!