Skip to content

Roadmap

Where docker-net-dhcp is going over roughly the next year, and, the more useful half, where it deliberately is not going.

This page is direction, not a delivery schedule. The project is solo-maintained, so nothing here carries a date. The milestone links below decide what is in a release; this page follows them.

Milestones

Release State Theme Milestone
v2.3.0 released The host plumbing an operator does by hand today, and the gaps the IPAM shape still refuses milestone 31
v2.3.1 released IPv6 routes at Join, the fixed-MAC hand-over in IPAM mode, and diagrams of the plugin and the lab milestone 38
v2.4.0 planned The rest of IPv6, and the DHCP options the client does not read yet milestone 34
v2.5.0 planned CI consolidation and code debt; nothing a user sees milestone 35

v2.3.0, released

  • #902, a vlan option that puts the network on a tagged VLAN off the parent
  • #903, a bridge the plugin creates and owns
  • #904, link-local fallback where no server answers, off by default
  • #905, macvlan and ipvlan sub-mode options
  • #960, IPv6 in IPAM mode: the v6 exchange, the v6 record, and a DUID that survives a restart
  • #1029, the DHCPv6 Client FQDN option, so register_dns registers the AAAA record beside the A; without it a DHCPv6 lease carries no name
  • #1036, a require_mac option that refuses an endpoint with no Docker-supplied MAC
  • #1037, an mtu option that sets the endpoint link MTU explicitly
  • #1045, the code-scanning result is a required check on both protected branches
  • #1096, a page that says how the plugin is tested, linked from the README
  • #1108, the first screen of the README says what, how and why

v2.3.1, released

  • #1118, in IPAM mode the kept identity of a stopped container goes back to the container with that MAC, and with require_mac to no other
  • #1125, Join returns each IPv6 route once, an endpoint with no IPv6 address no longer crashes it, and ipv6_mode alone gets the IPv6 default route
  • #1126, an architecture page with a diagram and a picture of the real-server lab in the testing page

v2.4.0

  • #1027, IPv6-Only Preferred, DHCPv4 option 108
  • #1028, PREF64 from the Router Advertisement, the NAT64 prefix
  • #1030, Microsoft classless static routes, option 249, where 121 is absent
  • #1031, DHCPv4 Rapid Commit
  • #1119, DHCPv4 FORCERENEW with nonce authentication (RFC 3203, RFC 6704); an unauthenticated FORCERENEW is discarded and counted
  • #1120, user_class=, the DHCPv4 User Class option 77 (RFC 3004)
  • #1032, ipv6_iid=stable-privacy, the RFC 7217 interface identifier, with modified EUI-64 kept as the default
  • #1033, the DHCPv6 timezone options logged as the v4 ones are
  • #1034, the vendor-specific options logged
  • #1038, a network with no DHCPv6 server remembered for a bounded time, so a SLAAC-only segment stops paying a full solicitation per attach
  • #926, DHCPv6 Rapid Commit, the two-message exchange
  • #927, DHCPv6 temporary addresses (IA_TA)
  • #214, DHCPv6 prefix delegation (IA_PD), designed first
  • #859, the whole DHCPv6 NTP server list
  • #1035, one multi-architecture manifest list per tag, which also settles the arm64 claim in the table below

v2.5.0

  • #733, the tracking issue for the CI consolidation programme
  • #744, one subject discovery, one refusal and one collation in a shared shell library
  • #745, six gates merged, and one proved removable
  • #746, the four integration lanes reduced to one reusable workflow
  • #747, four detectors modelling one vendor scheduler collapsed into one
  • #748, the process tier trimmed and frozen until there is a second maintainer
  • #749, gate expiry, the premise rule turned on the gates themselves
  • #798, a publisher outside the standard build-and-push shape is dropped from a gate's population
  • #799, nothing observes the release runbook against the release workflow
  • #856, nothing checks the release notes' breaking-change table against the code
  • #861, nothing checks that a Go comment still describes its code
  • #886, the canonical runner-pool facts are never compared to the live runner count
  • #680, a host AppArmor profile silently disables the kea fixture
  • #690, what each capability in config.json buys, measured by a matrix job
  • #657, the netlink seam enforced, so the endpoint paths can be unit-tested
  • #674, what a version number promises, written down
  • #178, the frozen docker/docker module replaced by the ones moby publishes

The release line

flowchart LR
    v20["v2.0<br/>the project's own<br/>DHCP engine"]
    v21["v2.1<br/>the leased address in<br/>Docker's IPAM"]
    v22["v2.2<br/>IPv6 modes and<br/>router discovery"]
    v23["v2.3<br/>host plumbing"]
    v24["v2.4<br/>the rest of IPv6"]
    v25["v2.5<br/>CI and code debt"]
    v20 --> v21 --> v22 --> v23 --> v24 --> v25
    classDef planned stroke-dasharray: 6 4
    class v24,v25 planned

v2.0, v2.1, v2.2 and v2.3 are released, and v2.4 and v2.5 are planned in that order; the planned ones are the dashed nodes. There are no dates. Every release, patches included, is in the release notes.

The bar every feature is measured against

One docker network create line, then plain networks: [lan-dhcp] in any Compose file. No static IPs, no sidecars, no per-container plumbing, no entrypoint script that has to know it is running on this network.

That bar decides most design arguments before they start. A workaround a user has to script around the plugin is read here as a bug report against this principle. #125 is the worked example: containers landed on eth0, eth1 and so on in attach order, users scripted around the name, and the plugin now returns the requested interface name to the engine.

Where the project is today

This branch is the 2.x line. The plugin leases through the project's own in-tree DHCP client library, for IPv4 and IPv6, over bridge, macvlan and ipvlan, and serves Docker's IPAM contract as well as its network driver contract.

The driver reference is the authority on what exists and what each option does, and the release notes are the record of what each tag changed. Where this page disagrees with either of them, this page is wrong.

Direction

Themes, in rough order of how much they change for a user. The issue numbers are anchors; the order is not a queue.

Theme State Anchors
Quiet addressing faults RFC 5227 conflict detection runs inside the container's namespace for the life of the lease; every new failure mode gets a counter as well as a log line #524
Lease identity across recreates the DHCPv6 half shipped with 2.0; the deterministic MAC is blocked upstream and the ipvlan client-id carries no milestone #218, #219, #895
arm64 a native arm64 runner executes the full integration suite as a release-candidate gate, and the shipping shape is per-architecture tags #507, #531
A test substrate that cannot lie a test that only passes once something is weakened is treated as a bug report, and a gate enforces it #403
Supply chain and documentation releases are signed, provenanced, SBOM'd and reproducible; the remaining pull is the OpenSSF silver and gold criteria #452
The reasoning behind each theme

Quiet addressing faults. The hard failures were solved first; the quiet ones are the current work. v1.6.0 added conflict detection (#524) because the plugin accepted an address a statically-configured host already held and every counter stayed at zero. The container came up, Docker reported an address, and nothing anywhere said otherwise. 2.0 moved the check off the parent link and into the DHCP client, as RFC 5227 Address Conflict Detection running inside the container's own namespace for the whole life of the lease, with conflict_check choosing who pays for it. A counter that can read clean while the feature is broken is treated as an unfinished instrument.

Lease identity across recreates. A DHCP server keys on identity, so address stability is an identity problem. #218, the deterministic MAC, is backlog and blocked upstream. #219, a stable client-id for ipvlan where every child shares the parent's MAC, carries no milestone. 2.0 settled the DHCPv6 half: an ipvlan endpoint now gets a DUID of its own (#895).

arm64. Users asked for it and it works, proven on real hardware (#531). Per-architecture tags (vX.Y.Z-arm64, latest-arm64) were first published with v1.7.0 (#507). This page long held that a Docker plugin cannot be installed from a multi-architecture manifest list at all, so the architecture lives in the tag. That claim is contested by a registry that serves one, and #1035 measures it.

A test substrate that cannot lie. This is infrastructure work with a user-visible reason: on this project, every timing crutch removed from CI turned out to be hiding a real defect. An opt-out helper added to make a restart test pass hid a user-facing docker restart failure for months. The question a loaded host raised, whether a slow Join leaves a container without a renewal client, was answered in 2.2 by making the attach path readable: every attach that completes is timed, and those durations with the namespace gauges say how close a host runs to its budget (#403).

Supply chain and documentation. The OpenSSF criteria are tracked as ordinary issues and generally translate into something concrete. This page exists because documentation_roadmap is one of them (#452).

Blocked upstream

Two features cannot ship until Docker's own engine carries a change. They stay open on purpose:

Here Needs Upstream
#218, the deterministic MAC network drivers to receive the endpoint name at CreateEndpoint, as IPAM drivers already do moby/moby#52870 (issue), moby/moby#52871 (PR, open)
#949, ipvlan with this plugin as the IPAM driver Docker to leave the container's interface MAC alone at start when the link cannot change it. Today it sets the MAC it generated for the IPAM driver, and every ipvlan container fails to start not filed yet

For #218, both halves were filed in June 2026. The endpoint-name change (moby/moby#52871) is still awaiting review, and #218 will not be closed as "won't fix" while that is the only thing in the way. This plugin's own half is written and waiting.

The second upstream dependency has moved. The interface_name pass-through (moby/moby#52866) merged and shipped in moby engine 29.8.0, and #125 closed with it. Engines below that boundary ignore a remote driver's requested interface name, the integration lane runs 29.8.0, and the plugin counts ifname_unsupported below it.

Out of scope

These are decided, and the reasoning is recorded so a contributor can read it before writing the PR.

Decision Reason Anchor
It will not become a DHCP server your existing server is the authority; a second one would recreate the problem the plugin solves #111
It will not change interfaces the host already has the plugin reads host configuration and does not own it; the links it creates for its own networks are listed below #902, #903
It will not gain a static-IP workflow an address that must be fixed is fixed where addresses are decided, in a reservation on the DHCP server n/a
It will not ask for more privileges to buy a feature a capability added to config.json forces every operator to re-approve the plugin on upgrade #725
It will not detect a conflicting container on the same host RFC 5227 runs on the container's own link, and macvlan parent and child isolation hides a sibling that has taken our address #528
It refuses ipvlan L3 and L3S, see reference an L3 child sends no broadcast, so DHCP cannot start n/a
It will not run its arm64 verification under qemu-user or binfmt measured: the emulated plugin could not acquire a lease at all, and arm64 verification runs on real hardware #531
It will not backport security fixes only the latest release is supported, and upgrading is one docker plugin install SECURITY.md
It will not carry AI-assistant attribution in its history commits and PRs are signed by a person who stands behind them, and a CI check enforces it n/a
The reasoning behind the refusals

A second DHCP server. No lease serving, no built-in pool, no failover of its own. Interoperating with more than one server is a different question, and v1.8.0 answered it: dhcp_servers and dhcp_deny_servers decide which existing server a network leases from (#111). That stays on this side of the line: the plugin picks among authorities and never becomes one.

Host interfaces. The plugin will not bring a NIC up, add an address, or edit netplan or systemd-networkd. Two host links are inside this rule, because the plugin creates them for its own networks and removes them with the last one: since v2.3.0 a bridge made from a spare NIC that carries no address, when a bridge-mode network sets parent (#903), and a VLAN sub-interface of the parent (#902).

Static IPs. A per-container static-IP option would be a second, silently conflicting IPAM.

Privileges. Adding a capability to config.json forces every operator to re-approve the plugin's privileges on upgrade, and refusing that trade is the standing answer.

2.0 is what this rule costs when it is met head-on. The 2.0 line does add CAP_NET_RAW to config.json, and every operator upgrading onto it re-approves. It buys no feature: the DHCP exchange runs on an interface with no address, which requires an AF_PACKET socket on the ordinary path for every endpoint, with no configuration in which the plugin works without it. The power is unchanged, because the capability is in the OCI default set and the process always held it, so what the line bought is an honest manifest and what it cost is the prompt. Read the rule as written: never add one to buy a feature, and pay the re-approval out loud when the plugin genuinely needs the grant it is already exercising. See #725, whose title says the capability is already granted, which is true of the effective set the process runs with.

A conflicting container on the same host. macvlan's parent and child isolation, the same property that keeps a sibling from disturbing us, also hides a sibling that has taken our address. That is excluded by construction and is not pending work (#528).

Emulated arm64. On the 1.x client, which opened a NETLINK_GENERIC socket qemu-user does not translate, the emulated plugin could not acquire a lease at all. The 2.0 client is a different program and has not been re-measured under emulation; the conclusion is unchanged either way, because arm64 verification runs on real hardware and there is nothing to be gained by finding out which syscall the emulator drops next.

How this page is kept honest

The release runbook makes a top-to-bottom documentation review a release step, and this page is part of it. The milestone table and the lists under it are re-read against the tracker there. If a theme above has gone a year without motion, or a refusal has quietly become something the project does, that review is where it gets corrected.