How to Assign IPv6 Addresses to LXD Containers on a VPS

Good news for the future of the Internet: IPv6 connectivity is an increasingly common feature offered by VPS providers these days. Unfortunately, many of them also cheap out. Instead of providing a true IPv6 prefix like 2607:f8b0:4004:811::1/64, they’ll provision you some arbitrary range, like the set of 10 addresses between 2607:f8b0:4004:811::1/128 and 2607:f8b0:4004:811::10/128.

I can’t fathom why the providers do this. There are literally trillions upon trillions of IPv6 addresses; if you can afford to hand out IPv4 addresses at no extra charge, you could surely afford to give everyone a /48 prefix, too. Alas, such is the cloud business.

I use my VPSes to run containers using Ubuntu’s LXD stack, and my current provider,, gives me 10 IPv6 addresses to work with. The other day, I was trying to figure out how to assign each address to a container in this scenario—only to find that no such guide existed.

  • This guy accomplished this with NAT66. (Ew.) Unfortunately, this is your only option if your host only provides a single /128 address.
  • This guy avoided NAT, but he did use an old program (npd6) that’s no longer relevant for modern kernels. He was also writing for LXC, not LXD.

So here, I’m going to present my solution, which avoids NAT66, doesn’t rely on manual firewall rules, and is tailored for the Ubuntu sysadmin stack (LXD, systemd, netplan).

Set up LXD networking

When setting up LXD, say “yes” to IPv6 addressing, but “no” to IPv6 NAT. Then add the addresses you want to assign to your containers as extra routes on the LXD bridge, like so:

$ lxc network set lxdbr0 ipv6.routes '2607:f8b0:4004:811::2/128, 2607:f8b0:4004:811::3/128'

The host’s network config should look something like this:

$ lxc network show lxdbr0
  ipv4.nat: "true"
  ipv6.address: fc02::1/120
  ipv6.dhcp.stateful: "true"
  ipv6.routes: 2607:f8b0:4004:811::2/128, 2607:f8b0:4004:811::3/128
$ ip -6 route
2607:f8b0:4004:811::2 dev lxdbr0 proto static metric 1024 pref medium
2607:f8b0:4004:811::3 dev lxdbr0 proto static metric 1024 pref medium

Set up NDP proxies

The kernel needs to know to advertise the containers’ addresses using the IPv6 neighbor discovery protocol (NDP). You do this using the ip neighbour add proxy (yes, British spelling) command for each address:

$ ip -6 neighbour add proxy 2607:f8b0:4004:811::2 dev net0
$ ip -6 neighbour add proxy 2607:f8b0:4004:811::3 dev net0
$ ip -6 neighbour list proxy
2607:f8b0:4004:811::2 dev net0  proxy
2607:f8b0:4004:811::3 dev net0  proxy

This list needs to be recreated each boot, so I have a systemd service to run these commands:

# /etc/systemd/system/proxy-ndp.service

Description=Announce all IPv6 addresses allocated to this server

ExecStart=/sbin/ip -6 neighbour add proxy 2607:f8b0:4004:811::2 dev net0
ExecStart=/sbin/ip -6 neighbour add proxy 2607:f8b0:4004:811::3 dev net0


Enable IPv6 packet forwarding and proxy relaying

The kernel disables these features by default, so you also need to modify these sysctl properties:

# /etc/sysctl.d/91-forward-ipv6.conf 


Set up container networking

Finally, you can assign each container the IPv6 address you desire. This configuration is done from within the container, just as if it were a virtual machine or a real computer. Each container already receives a private IPv6 address from LXD (like fc02::6e); you just need to assign its public address as an additional static address. Here’s how that’s done by hand using ip,

# ip -6 address add 2607:f8b0:4004:811::2 dev eth0

and here’s how it’s done in Ubuntu’s netplan:

# /etc/netplan/50-cloud-init.yaml

# This file is generated from information provided by
# the datasource.  Changes to it will not persist across an instance.
# To disable cloud-init's network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
    version: 2
            dhcp4: true
            dhcp6: true
            - 2607:f8b0:4004:811::2/128

It’s not necessary to specify a gateway (as in IPv4) thanks to the magic of IPv6 router advertisements.


And that’s it! Your containers should have IPv6 networking with Internet-reachable addresses now. Under the default libc configuration, traffic will be preferentially routed over IPv6.

# ip -6 route
2607:f8b0:4004:811::2 dev eth0 proto kernel metric 256 pref medium
fc02::/120 dev eth0 proto ra metric 100 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::c0c4:4aff:fedf:89a6 dev eth0 proto ra metric 100 mtu 1500 pref medium
# ping
PING (2a00:1450:400e:80b::200e)) 56 data bytes
64 bytes from (2a00:1450:400e:80b::200e): icmp_seq=1 ttl=57 time=5.16 ms
64 bytes from (2a00:1450:400e:80b::200e): icmp_seq=2 ttl=57 time=5.33 ms
64 bytes from (2a00:1450:400e:80b::200e): icmp_seq=3 ttl=57 time=5.49 ms
64 bytes from (2a00:1450:400e:80b::200e): icmp_seq=4 ttl=57 time=5.30 ms
--- ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 5.169/5.326/5.499/0.128 ms

I didn’t touch upon the IPv4 stack in this short tutorial, but you will most likely want to stick with LXD’s default configuration: DHCP and NAT on a /24.

$ lxc list
|       NAME       |  STATE  |       IPV4        |            IPV6              |    TYPE    | SNAPSHOTS |
| container1       | RUNNING | (eth0) | fc02::6e (eth0)              | PERSISTENT | 0         |
|                  |         |                   | 2607:f8b0:4004:811::2 (eth0) |            |           |
| container2       | RUNNING | (eth0)  | fc02::5 (eth0)               | PERSISTENT | 0         |
|                  |         |                   | 2607:f8b0:4004:811::3 (eth0) |            |           |

Enjoy having end-to-end connectivity on your containers, the way the Internet was meant to be experienced.

Walking with Southeast Bakersfield on MLK Jr. Day

Last week, I swung by the Wilson Library to attend Bakersfield’s 2019 Martin Luther King Jr. Day march. To me, it was a march that lacked bite, because the adjectives I would use to describe that event—docile, tame, by-the-book, ordered—are not words I would normally associate with a march for civil rights. Yet how else would I describe a civil rights march that had an itinerary? It had been sent out in the Bakersfield Californian in advance: set off at 6 o’ clock, march to the tune of “We Shall Overcome,” and then attend a service at the neighborhood church. As it turned out, we and our police and media escorts followed this kindergarten-field-trip schedule to the letter.

The march began just past 6. The crowd—about people 30 to 40 in size, dressed in coats and down jackets, appropriate for the chilly January night—was small, according to two of the marchers, both of whom had been regular attendants for years. One of them told me that the march was abbreviated, too, because it used to extend “all around town”; now, considering the age of the participants—mostly middle-aged, some elderly, several with Vietnam veteran’s caps—the march had been truncated to a half-mile walk to the community church. We hope more young people like you will join us in the future, both of them said.

Nonetheless, the parade moved surprisingly briskly. About a third of the marchers formed a line in front, standing side-by-side with their arms locked, singing “We Shall Overcome.” The rest formed a vaguely organized mass of people, some of them talking among themselves, some of them joining in the singing, some of them holding up their smartphones to recording the proceedings.

As we walked, the cameramen darted nimbly in and out of the crowd, capturing the scene from all sorts of angles—one of them equipped with a large, brilliant rectangular torch as bright as any construction site work light—while the police barricaded the intersecting roads with their squad cars and shut off the lone traffic signal along the marchers’ route. The attention our crowd received was proportionate to its small size, for there was little foot traffic in this ramshackle corner of Southeast Bakersfield—populated as it was with dilapidated strip malls, humble taco trucks, and working-class ranch homes—but some neighbors did observe the goings-on, out of curiosity, from the chain link fences that lined their modest front yards.

But where was the shock value, the spontaneity? I kept waiting for someone to take the initiative, to heckle the cops or to shout a defiant headliner into the cameras. Public demonstrations are supposed to be calls to action, not just background roll on the evening news. After all, for every feelgood speech that MLK made, there was also a sit-in, bus boycott, freedom ride, or some other provocation that deliberately pissed a lot of people off.

Okay, I suppose an MLK march has to be inclusive of all ages, not just rebellious 20-somethings like myself. Besides, who am I to assume what Bakersfield’s Black community wants and needs? Bakersfield is a conservative town, and the issues faced by the coastal metropolises—affirmative action and blatant racism, among other issues raised by attendees at their MLK marches—seem many worlds away on these sleepy streets.

There was an informal meet and greet before the march began at 6, through which I was treated to an anthropological cross-section of the marchers. I saw friends meeting old friends and exchanging greetings, news, and small talk under the harsh streetlights of the Wilson Library parking lot. I also saw church buses, vans, and carpools bringing more groups of people to the march. As one might expect, most of the marchers were Black, but people of all races were in attendance—some Whites, some Latinos, and a few Asians—which would have pleased one spirited marcher, who made an impromptu speech expressing hope for a multiracial march in the spirit of MLK Jr. himself.

Surrounding us were the policemen who would close the streets and the television crews who would send images of the march to the rest of the city. Already, the cameramen were mingling in the crowd, shooting footage and interviewing some of us. The policemen stood idly by their squad cars, watching, preparing to halt traffic on Wilson Road on a moment’s notice. Later on, when we got the church, I saw some of them shaking hands with the marchers.

Sitting in one of the pews of the Church of Christ, watching Black leaders from Arvin and Fresno deliver speeches that struck a delicate balance between religious sermon and political lecture—Moses’ delivery of the Israelites out of Egypt as a metaphor for the civil rights struggle was the highlight of the night, as was a firm reminder that social equality had not brought about economic equality—I realized I had learned more about Black culture than I had in all my years of growing up in Bakersfield. The media, to their credit, were watching diligently, too. They had set up a camera inside of the chapel, and one of their anchors also live-streamed a very moving performance by the church’s choir.

Maybe I’m the one who has the wrong idea about MLK Jr. Day. The event is as much a focal point for the Black community as it is a rallying cry for social justice. Sure, we didn’t create headlines, but how important is a few minutes of a fame in our present-day outrage culture, anyway? I like to think that between the marchers, the church, the police, and the media, we all learned a little bit more about each other that evening. Isn’t that what Mr. King would have wanted?