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!