BLOG
Building OpenWRT with Docker
I’ve run OpenWRT on my home router for a long time, and these days I maintain a couple of packages for the project. In order to make most efficient use of the hardware resources on my router, I run a custom build of the OpenWRT firmware with some default features removed and others added. For example, I install bind and ipsec-tools, while I disable the web UI in order to save space.
There are quite a few packages required for the OpenWRT build process. I don’t necessarily want all of these packages installed on my main machine, nor do I want to maintain a VM for the build environment. So I investigated using Docker for this.
Starting from a base jessie image, which I created using the Docker debootstrap wrapper, the first step was to construct a Dockerfile containing instructions on how to set up the build environment and create a non-root user to perform the build:
FROM jessie:latest
MAINTAINER Noah Meyerhans <frodo@morgul.net>
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get -y install \
asciidoc bash bc binutils bzip2 fastjar flex git-core g++ gcc
util-linux gawk libgtk2.0-dev intltool jikespg zlib1g-dev make \
genisoimage libncurses5-dev libssl-dev patch perl-modules \
python2.7-dev rsync ruby sdcc unzip wget gettext xsltproc \
libboost1.55-dev libxml-parser-perl libusb-dev bin86 bcc sharutils \
subversion
RUN adduser --disabled-password --uid 1000 --gecos "Docker Builder,,," builder
And we generate a docker image based on this Dockerfile per the docker build documentation. At this point, we’ve got a basic image that does what we want. To initialize the build environment (download package sources, etc), I might run:
docker run -v ~/src/openwrt:/src/openwrt -u builder -t -i jessie/openwrt
sh -c "cd /src/openwrt/openwrt && scripts/feeds update -a"
Or configure the system:
docker run -v ~/src/openwrt:/src/openwrt -u builder -t -i
jessie/openwrt make -C /src/openwrt/openwrt menuconfig
And finally, build the OpenWRT image itself:
docker run -v ~/src/openwrt:/src/openwrt -u builder -t -i
jessie/openwrt make -C /src/openwrt/openwrt -j3
The -v ~/src/openwrt:/src/openwrt
flags tell docker to bind mount my
~/src/openwrt directory (which I’d previously cloned using git) to
/src/openwrt inside the running container. Without this, one might be
tempted to clone the git repo directly into the container at runtime,
but the changes to non-bind-mount filesystems are lost when the
container terminates. This could be suitable for an autobuild
environment, in which the sources are cloned at the start of the build
and any generated artifacts are archived externally at the end, but it
isn’t suitable for a dev environment where I might be making and
testing small changes at a relatively high frequency.
The -u builder
flags tell docker to run the given commands as the
builder user inside the container. Recall that builder was created
with UID 1000 in the Dockerfile. Since I’m storing the source and
artifacts in a bind-mounted directory, all saved files will be created
with this UID. Since UID 1000 happens to be my UID on my laptop, this
is fine. Any files created by builder inside the container will be
owned by me outside the container. However, this container should not
have to rely on a user with a given UID running it! I’m not sure what
the right way to approach this problem is within Docker. It may be
that someone using my image should create their own derivative image
that creates a user with the appropriate UID (creation of this
derivative image is a cheap operation in Docker). Alternatively,
whatever Docker init system is used could start as root, add a new
user with a specific UID, and execute the build commands as that new
user. Neither of these seems as clean as it could be, though.
In general, Docker seems quite useful for such a build environment. It’s easy to set up, and it makes it very easy to generate and share a common collection of packages and configuration. Because images are self-contained, I can reclaim a bunch of disk space by simple executing “docker rmi”.