Introduction
When connecting your LXC container to an external network, you have several methods to choose from. One option is the “routed” configuration, where the host actively routes traffic between its interfaces and the containers. Alternatively, you can use a “bridged” configuration, allowing the container to connect to a network bridge on the host, making it appear as a regular device on the same network.
In a previous article, I demonstrated how to set up an LXC container using the “routed” configuration. In this article, however, the “bridged” approach will be covered.
Step 0: Routed vs. Bridged
When connecting an LXC container with a static IP to your local network, you can choose between bridged and routed networking, depending on your use case. Bridged networking is ideal when you want your container to behave like a physical machine on your network, with full access to other devices and services. It uses a Linux bridge (e.g., lxcbr0
(like the default LXC managed bridge) or a custom br0
(like a unmanaged bridge from the host)) to connect the container to the same subnet as the host, allowing seamless communication with other local devices. This approach is best for scenarios requiring DHCP assignments, multicast traffic, or direct LAN access.
On the other hand, routed networking is suitable when you need stricter isolation while still allowing outbound traffic. Here, the host routes packets between the container and the external network using static routes or NAT, ensuring better security and control. This is useful when the container does not need direct access to all local network devices but should still communicate externally. If your container needs a dedicated IP on the LAN, use bridged mode; if you need tighter control over networking and security, routed mode is the better choice.
Step 1: Set up the unmanaged bridge on the host
Just so you know before we begin: The “unmanaged” only means that LXC is not in control of the bridge, as the bridge is created by the host itself. This allows us to specify that the bridge should be connected to specific interfaces on the host. This article shows how to do that with Ubuntu’s netplan
.
First off, please install bridge-utils
:
apt-get install bridge-utils
And continue to define your netplan. Be aware that you might have different files in your netplan
directory already. So either work with the existing ones or create a consolidated new one, like we do here:
nano /etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: networkd
ethernets:
nic0:
set-name: nic0
match:
macaddress: c8:dd:40:23:31:60
dhcp4: no
nic1:
set-name: nic1
match:
macaddress: c8:dd:40:28:4c:ca
dhcp4: no
bridges:
br0:
interfaces:
- nic0
dhcp4: no
addresses:
- 192.168.20.170/24
nameservers:
addresses:
- 192.168.20.1
routes:
- to: default
via: 192.168.20.1
br1:
interfaces:
- nic1
dhcp4: no
addresses:
- 192.168.21.170/24
nameservers:
addresses:
- 192.168.21.1
In the ethernets
section, I rename my two NICs to nic0
and nic1
based on their MAC address. This is not strictly necessary, but tidies up the naming for later usage.
The important bit is the bridges
section: Here we create the two (unmanaged) bridges br0
and br1
, define which NICs should bind to them and also setup the static IP for the hosts perspective. The routes
block defines the gateway to which the traffic is directed to.
Save your file and set the netplan
into action with
netplan apply
Step 2: Create a template routing profile
Before creating the LXC container, we must first set up a template for the networking profile. This template will serve as the “prototype” for all future profiles.
lxc profile create bridged
Proceed to edit this profile:
EDITOR=nano lxc profile edit bridged
And provide the template with following payload:
name: bridged
description: Route container traffic via unmanaged bridge
config:
user.network-config: |
version: 2
ethernets:
eth0:
addresses:
- <DUMMY-IP>/24
nameservers:
addresses:
- <YOUR-DNS-IP>
routes:
- to: default
via: <YOUR-GATEWAY-IP>
on-link: true
devices:
eth0:
name: eth0
nictype: bridged
parent: br0
type: nic
This structure serves two purposes: For one the devices
block defines that the container should use the already present unmanaged bridge on the host while the config
block sets up the static IP address the container should be using.
Please also make sure to adjust the interface and / or bridge names if you chose different names for your setup. Proceed by saving and exiting the configuration file.
Step 3: Create an adjusted routing profile from the template
Now that the template routing profile is set up, create a copy of it to serve as the actual configuration profile for your LXC container. Be sure to replace the placeholder with a value that fits your specific scenario.
lxc profile copy bridged bridged_<INTENDED-IP-OF-LXC-CONTAINER>
EDITOR=nano lxc profile edit bridged_<INTENDED-IP-OF-LXC-CONTAINER>
name: bridged_<INTENDED-IP-OF-LXC-CONTAINER>
description: Route container traffic via unmanaged bridge
config:
user.network-config: |
version: 2
ethernets:
eth0:
addresses:
- <INTENDED-IP-OF-LXC-CONTAINER>/24
nameservers:
addresses:
- <YOUR-DNS-IP>
routes:
- to: default
via: <YOUR-GATEWAY-IP>
on-link: true
devices:
eth0:
name: eth0
nictype: bridged
parent: br0
type: nic
Now, you simply need to modify this copy of the original “prototype” bridged
file with the appropriate values for the upcoming LXC container.
Proceed by saving and exiting the configuration file.
Step 4: Create a LXC container with the routing profile
With the routing profile in place, you can now proceed to create the LXC container:
lxc launch <IMAGE-NAME> <INTENDED-NAME-OF-LXC-CONTAINER> --profile default --profile bridged_<INTENDED-IP-OF-LXC-CONTAINER>
Step 5: Finished!
That’s it! Your LXC container should now be routing its traffic through the unmanaged bridge to the host’s interface, connecting it to the external network.