Docker on Tinc in Docker

Tinc is an open-source virtual private network (VPN) solution. It’s GPL, peer-to-peer (no central server) and battle tested.

This tutorial explains how to setup tinc the easy way, by having it run inside a docker container. Then how to run services with docker that are exposed only to the members of the VPN. My idea for setting this up is to have a virtual data center managed by Mesos, on cheap dedicated servers without a firewall, but this is irrelevant to this tutorial.

For this job, we will use the jenserat/tinc docker image, which has an automated build active on dockerhub. I’ve build a small utility around it called quicktinc, which knows to do less things than tinc can, but is way simpler to setup. Let’s start!


For this tutorial, I used 2 Ubuntu 14.04 servers, but there’s not reasons why it should be limited to that setup. The first thing is to decide on our configuration, here’s mine:

  • network:
  • network name: demonet
  • nodes:
    • node1, with public IP
    • node2, with public IP
  • private IPs:,
  • config directory: /srv/tinc (constrained by quicktinc)

Init script

To make the VPN useful, we’ll probably add quite a few nodes to it. We’d better automate their registration as much as possible. I wrote this simple initialization script, called quicktinc, available on GitHub:

Let’s install it on both nodes with:

curl | sudo tee /usr/local/bin/quicktinc
sudo chmod +x /usr/local/bin/quicktinc

Node 1

Now let’s setup our first host: node1. Run quicktinc with the following arguments:

quicktinc --net=demonet --node=node1 --public-ip= --private-ip= --up

(to replace with your own values).

This will create the initial configuration files in /srv/tinc/demonet. I added the --up argument, so this will also start the daemon. We’re all setup!

If we didn’t use –up, we would have had to run this:

docker run -d --restart=always --net=host --device=/dev/net/tun --cap-add NET_ADMIN --volume /srv/tinc:/etc/tinc jenserat/tinc -n demonet start -D

Node 2

On node2, run:

quicktinc --net=demonet --node=node2 --public-ip= --private-ip= --connect-to=node1 --up

It will start the tinc daemon, but won’t work right away. For two nodes to able to communicate with each others, they need to know each others, by having their respective definitions in /srv/tinc/demonet/hosts.

So let’s copy /srv/tinc/demonet/hosts/node1 from node1 to node2, and also copy /srv/tinc/demonet/hosts/node2 from node2 to node1. Note that you don’t have to restart tinc when you add more nodes in the hosts directory.

While this is kind of ok with two nodes, a better solution would be to use version control for this hosts directory…

Now we should be all set. A quick ping from node1, and ping from node2 to make sure it works. All good, great!

Run a service with Docker on the VPN

Now for the last part, we wan’t to run a service only available within the VPN. By default docker binds the ports to, so we just have to restrict that to the right IP address. It’s a little tricky if we want our service startup script to be host independent. So shell magic is required to retrieve the IP address linked with the VPN network interface.

TUN0_IP=$(ifconfig tun | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}')

Then we can start our service, bound to the VPN interface only:

docker run --rm -p $TUN0_IP:8000:80 nginx

If you run the above from node1, then let’s test from node2:


You should get some HTML back: congrats!

You can also make sure that node1’s port 8000 is not available from the node’s public IP.


It doesn’t work, which means our service is restricted for use within the VPN.

Go further

  • Setup some version control, with automated pulling of the “hosts” directory.
  • Automatically push added node’s host file to git when adding them.

Feel free to contribute to quicktinc to make it better!

I’m a consultant and developer for Mobile, Web, Games and Apps projects. I co-founded Fovea 8 years ago.

Tagged with: , , ,
Posted in Blog