Self-hosted service
openwrt/docker avatar
openwrt/docker

openwrt/docker: the OpenWrt build tools packaged as containers

Docker containers of the ImageBuilder and SDK

628 stars105 forksShellGPL-2.0

At a glance

What is it?
The repository publishes SDK, ImageBuilder and rootfs containers for OpenWrt to three registries, which lets a macOS, Windows or CI machine build a package or a firmware image, at the cost of a download step that recent snapshots made unavoidable.
Who is it for?
openwrt/docker is the right tool for a developer on macOS or Windows who needs to compile an OpenWrt package or produce a firmware image, and for a CI job that wants the same toolchain OpenWrt's own buildbot uses rather than a locally maintained one.
Can I use it commercially?
Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 35 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Three containers around the OpenWrt toolchain

This repository contains the files to create OpenWrt containers. The stated primary use is the project's own continuous integration, with an explicit invitation to use the scripts to build the containers yourself.

There are three images, and each one wraps a different OpenWrt artifact. The sdk container compiles OpenWrt packages, the imagebuilder container creates firmware images, and the rootfs container is for testing software inside an OpenWrt runtime. The first two are based on the same containers used for the Buildbot infrastructure, which is the useful part of the design: the environment a developer gets locally is the environment the official builds run in, not a lookalike maintained by a third party.

That is who this is for. A developer on macOS or Windows who wants to compile a package or assemble a firmware image without installing a Linux toolchain, and a CI system that wants a reproducible container rather than a machine with the right packages installed on it.

The implementation is shell. The repository is small, with a Dockerfile, a Dockerfile.rootfs, a setup.sh, a keys directory and a .github directory, and the work is done by scripts that download the real OpenWrt archives and unpack them into the container.

Since OpenWrt 24.10 a snapshot container ships a script, not binaries

The warning at the top of the documentation is the single most important operational fact about these images, and it is easy to miss.

Starting with the branch of OpenWrt 24.10, any snapshot, also called a nightly, build no longer contains the actual binaries. The container contains a setup.sh script instead. The environment variables are set automatically per container, and running that script downloads the correct archive containing the SDK, the ImageBuilder or the rootfs for the version and target the image was built for. The stated reason is that this dramatically reduces bandwidth and storage usage, and the documentation apologises for the inconvenience.

So the size of the image is no longer a good guide to what you get, and a container that starts does not mean it is ready. Every example in the documentation starts with a check for whether the scripts directory exists, and only calls setup.sh when it does not.

For a CI job, this is a feature: the container stays small, the download happens on the runner that needs it, and the image is shared by every target of the same version. For a workstation with a slow or absent network, it is a step in the way, and it is the reason a pinned release image and a nightly image behave differently under the same command.

Compiling a package with the SDK container

The SDK container is documented with a worked example that builds a single package and leaves the binary on the host.

bash
docker run --rm -v "$(pwd)"/bin/:/builder/bin -it openwrt/sdk
# inside the Docker container
[ ! -d ./scripts ] && ./setup.sh
./scripts/feeds update packages
make defconfig
./scripts/feeds install tmate
make package/tmate/{clean,compile} -j$(nproc)

The mount of the host bin directory into /builder/bin is what puts the compiled artefact back on your machine, and the whole run is a single interactive container that is removed on exit. The sequence inside is the standard OpenWrt package flow: update the package feeds, run defconfig, install a feed package, then build just that package with a parallel job count taken from the number of processors.

The tags are how you pick what you get. The format is target and subtarget, optionally followed by a branch, a tag or a version, and there is also an architecture only form. A branch looks like openwrt-22.03, a tag like v22.03.4, and a version like 21.02.3, and for daily builds you use either main or SNAPSHOT.

That last point is the one to internalise. A tag pinned to a released version gives you the same SDK OpenWrt published, while main and SNAPSHOT move every day.

Producing a firmware image with the ImageBuilder container

The ImageBuilder container assembles firmware rather than compiling packages, and its example sets a platform, a version, a device profile and a package list before running the build.

bash
PLATFORM=ath79-generic
VERSION=25.12.5
PROFILE=tplink_archer-c7-v2
PACKAGES="ath10k-firmware-qca988x kmod-ath10k kmod-usb-ledtrig-usbport kmod-usb-net-cdc-ether kmod-usb2 luci luci-ssl luci-app-sqm luci-app-attendedsysupgrade owut -ppp -ppp-mod-pppoe -luci-proto-ppp -ath10k-firmware-qca988x-ct -kmod-ath10k-ct"
mkdir -p openwrt-${PLATFORM}-${VERSION}/{bin,dl}
cd openwrt-${PLATFORM}-${VERSION}

docker run --rm \
  --volume "$(pwd)"/bin/:/builder/bin \
  --volume "$(pwd)"/dl:/builder/dl \
  openwrt/imagebuilder:${PLATFORM}-${VERSION} \
  sh -c "
    [ ! -d ./scripts ] && ./setup.sh;
    make image PROFILE='${PROFILE}' PACKAGES='${PACKAGES}'
  "

The variables are meant to be edited for your needs, and the documentation notes they can be inlined to turn the whole thing into a one liner. Built images land in the bin directory and cached packages land in dl.

Two practical warnings come with it. Packages from different platforms or versions can share a package name while having different checksums, which causes errors during a build, so working directories should not be shared between different image builders. And on an SELinux enabled system such as RHEL, the volumes must be mounted with the :z flag, which changes the mount line to include :z after the container path.

Podman works too, with one difference: the user account has to be mapped to the same UID inside the container, using the --userns=keep-id option, and the documentation gives the parallel invocation. For snapshots, set VERSION=SNAPSHOT.

The rootfs container is labelled experimental for a reason

The third image is an unpackaged version of the OpenWrt root filesystem for different architectures, and it carries an explicit warning. The OpenWrt runtime uses multiple active services to work, and it is not really suited to being a container, so the documentation says this rootfs should only be used for special cases such as CI testing.

That is a useful admission, because an unpackaged root filesystem is exactly the thing that misbehaves under a container runtime. There is no init system managing the services, no process supervisor, and the expectation that the filesystem is the whole operating system rather than a subset of it.

The example runs it as an interactive container and treats it as a package sandbox.

bash
docker run --rm -it openwrt/rootfs
# inside the Docker container
[ ! -d ./scripts ] && ./setup.sh
mkdir /var/lock/
opkg update
opkg install tmate
tmate

The mkdir of /var/lock is the kind of adjustment that tells you what you are dealing with. The build itself needs the rootfs folder in the repository to be modified slightly so it works within Docker, with additional files for the rootfs added there before building.

The tags for this image are architecture names, and the documentation lists the equivalents: x86/64 or x86_64, x86/generic or i386_pentium4, x86/geode or i386_pentium-mmx, armvirt/32 or arm_cortex-a15_neon-vfpv4, armvirt/64 or aarch64_cortex-a53, malta/be or mips_24kc, and mvebu/cortexa9 or arm_cortex-a9_vfpv3-d16.

Three registries, a CI workflow, and the secrets behind it

All containers are mirrored to three registries under the openwrt account, which matters for corporate networks that block one registry and allow another.

On Docker Hub the images are openwrt/sdk, openwrt/imagebuilder and openwrt/rootfs, and a note records that the account was switched from openwrtorg to openwrt. On ghcr.io the same three names live under the openwrt package namespace, and on quay.io they are under the openwrt organisation with the same three repository names.

The GitHub Actions workflow at .github/workflows/containers.yml builds the containers and pushes them to a registry path built from the repository owner, so a fork pushes to its own namespace rather than overwriting the official images. Pushing to ghcr.io works out of the box with the built in GITHUB_TOKEN. Docker Hub and Quay need secrets configured under the repository settings, and the documentation names them: DOCKER_USER with DOCKER_TOKEN for the Docker Hub user and its personal access token, and QUAY_USER with QUAY_TOKEN for a Quay user or robot account with write permission on the existing sdk, imagebuilder and rootfs repositories. A registry without both its user and a token is simply skipped, and the namespace can be changed with DOCKER_NAMESPACE or the Quay equivalent.

The repository itself publishes no GitHub releases, so there is nothing to download from the releases page. The container tags are the release mechanism.

Where these containers are the wrong tool

Four limits are worth planning around.

The first is the scope of the ImageBuilder. It creates firmware images from a profile and a package list, using prebuilt packages. It is not a full firmware build, which compiles the kernel and the base system from source, and that is the SDK's job. A team that needs custom packages compiled into the firmware needs the SDK image, not the ImageBuilder one, and a team that needs a fully custom build needs a buildbot style environment.

The second is the network dependency of the snapshot images. Since 24.10, a nightly container contains a setup script rather than the payload, so a build that cannot reach downloads.openwrt.org fails at the first step. A pinned release image behaves differently, and the tag you choose is the variable that decides this.

The third is the working directory. The documentation warns against sharing directories between image builders because packages with the same name can have different checksums across platforms and versions. On a CI runner that means one directory per platform and version, and cleanup between them.

The fourth is the rootfs container, which the project itself marks experimental and unsuitable except for cases like CI testing. Building a service image on top of an unpackaged OpenWrt filesystem is possible, and it is also the fastest route to a system whose service supervision does not behave the way you expect.

Licence, maintenance and how to pin an image

The licence is GPL-2.0, with a LICENSE file at the root of the repository and a licence badge in the documentation. Since the repository mostly contains scripts and Dockerfiles rather than linked libraries, the practical licence question for most adopters is small, but a company with a policy on licence text should still record it. This is not legal advice.

On maintenance, the repository is not archived and the last push was on 2026-09-18. There are no GitHub releases, so there is no changelog of image versions to read, and the images themselves are the artefacts that change.

That makes pinning a specific decision. The tag scheme gives you a target and subtarget, optionally with a branch, a tag or a version, and you can use main or SNAPSHOT for daily builds. For a production pipeline, the sensible choice is a version tag such as 25.12.5, which pins the OpenWrt release, and the container then fetches the archive for exactly that version. For a nightly pipeline, SNAPSHOT is the point, and you accept that the payload changes under the same image name.

One more detail from the Dockerfile explains how the containers find the right archive. The build arguments set the download pattern, the target, the file host and the version path, and the keys directory is added to the container so the download can be verified.

dockerfile
ARG BASE_IMAGE=ghcr.io/openwrt/buildbot/buildworker-v3.11.8:v21

FROM $BASE_IMAGE
ARG USER=buildbot
ARG WORKDIR=/builder/
ARG CMD="/bin/bash"

So the base image is itself a buildbot worker, and the images are a thin layer that adds the OpenWrt download and unpacking on top of it.

Editorial conclusion

openwrt/docker is the right tool for a developer on macOS or Windows who needs to compile an OpenWrt package or produce a firmware image, and for a CI job that wants the same toolchain OpenWrt's own buildbot uses rather than a locally maintained one. It is the wrong tool for building a full firmware from source, which needs the SDK rather than the ImageBuilder, and for a build that must work with no network access, because a snapshot container downloads its payload when setup.sh runs. Before adopting, check the tag you pin against the release you need, since the tag format carries target, subtarget and version but the container body for a snapshot is fetched at run time, and keep a separate working directory per platform and version.

Frequently asked questions

What containers does the openwrt/docker repository publish?

Three: sdk, which compiles OpenWrt packages, imagebuilder, which creates firmware images, and rootfs, an experimental unpackaged OpenWrt runtime for testing. The sdk and imagebuilder containers are based on the same containers used for the OpenWrt buildbot infrastructure, and all three are mirrored to Docker Hub, ghcr.io and quay.io under the openwrt account.

Why do OpenWrt snapshot containers only contain a setup.sh script?

Starting with the OpenWrt 24.10 branch, snapshot or nightly builds no longer contain the actual binaries but a setup.sh script, with environment variables set per container so it downloads the correct archive for the SDK, ImageBuilder or rootfs. The stated reason is that this dramatically reduces bandwidth and storage usage, so a container that starts is not necessarily ready to build.

How do I choose the right tag for the openwrt/sdk or openwrt/imagebuilder image?

Tags follow the format target and subtarget with an optional branch, tag or version, and there is also an architecture only form. A branch looks like openwrt-22.03, a tag like v22.03.4 and a version like 21.02.3, and for daily builds you use either main or SNAPSHOT.

Can I run the OpenWrt ImageBuilder container with Podman?

Yes, but the user account has to be mapped to the same UID inside the container, so the run command needs the --userns=keep-id option in addition to the same volume mounts used with Docker. On SELinux enabled systems such as RHEL, the volumes must be mounted with the :z flag.

What credentials does the openwrt/docker CI workflow need to publish images?

Pushing to ghcr.io works out of the box with the built in GITHUB_TOKEN. Docker Hub needs DOCKER_USER and DOCKER_TOKEN, and Quay needs QUAY_USER and QUAY_TOKEN with write permission on the existing sdk, imagebuilder and rootfs repositories. A registry without both its user and a token is skipped.

Official sources

  1. Issues
  2. License: GPL-2.0
  3. openwrt/docker on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openwrt-docker.svg)](https://hysenlabs.com/projects/openwrt-docker)

Was this useful?