Skip to content


Sometimes you need an OpenVPN tunnel between your docker hosts and some other environment. I needed this to provide connectivity between swarm-deployed services like Home Assistant, and my IOT devices within my home LAN.

OpenVPN is one application which doesn't really work in a swarm-type deployment, since each host will typically require a unique certificate/key to connect to the VPN anyway.

In my case, I needed each docker node to connect via OpenVPN back to a pfsense instance, but there were a few gotchas related to OpenVPN at CentOS Atomic which I needed to address first.

SELinux for OpenVPN

Yes, SELinux. Install a custom policy permitting a docker container to create tun interfaces, like this:

cat << EOF > docker-openvpn.te
module docker-openvpn 1.0;

require {
    type svirt_lxc_net_t;
    class tun_socket create;

#============= svirt_lxc_net_t ==============
allow svirt_lxc_net_t self:tun_socket create;


checkmodule -M -m -o docker-openvpn.mod docker-openvpn.te
semodule_package -o docker-openvpn.pp -m docker-openvpn.mod
semodule -i docker-openvpn.pp

Insert the tun module

Even with the SELinux policy above, I still need to insert the "tun" module into the running kernel at the host-level, before a docker container can use it to create a tun interface.

Run the following to auto-insert the tun module on boot:

cat << EOF >> /etc/rc.d/rc.local
# Insert the "tun" module so that the vpn-client container can access /dev/net/tun
/sbin/modprobe tun
chmod 755 /etc/rc.d/rc.local

Connect the VPN

Finally, for each node, I exported client credentials, and SCP'd them over to the docker node, into /root/my-vpn-configs-here/. I also had to use the NET_ADMIN cap-add parameter, as illustrated below:

docker run -d --name vpn-client \
  --restart=always --cap-add=NET_ADMIN --net=host \
  --device /dev/net/tun \
  -v /root/my-vpn-configs-here:/vpn:z \
  ekristen/openvpn-client --config /vpn/my-host-config.ovpn

Now every time my node boots, it establishes a VPN tunnel back to my pfsense host and (by using custom configuration directives in OpenVPN) is assigned a static VPN IP.

Tip your waiter (sponsor me) 👏

Did you receive excellent service? Want to make your waiter happy? (..and support development of current and future recipes!) Sponsor me on Github / Patreon, or see the support page for more (free or paid) ways to say thank you! 👏

Flirt with waiter (subscribe) 💌

Want to know now when this recipe gets updated, or when future recipes are added? Subscribe to the RSS feed, or leave your email address below, and we'll keep you updated. (*double-opt-in, no monkey business, no spam)

Notify me 🔔

Be the first to know when recipes are added / improved!

    We won't send you spam. Unsubscribe at any time. No monkey-business.

    Powered By ConvertKit

    Your comments? 💬

    Last update: May 19, 2020