A deep dive into using Tailscale with Docker

Tailscale
7 Feb 202431:58

Summary

The video is abnormal, and we are working hard to fix it.
Please replace the link and try again.

Takeaways

  • 😀 今では、本番環境でコンテナを稼働させていることが一般的になりました。
  • 🚀 TailscaleとDockerを使用して、コンテナをTailnetに追加する方法とそのメリットについて説明します。
  • 🔐 Tailscaleをコンテナで実行することで、ACLを通じてアクセス制御が可能になり、リバースプロキシを置き換えることができます。
  • 🌐 Tailscaleは、暗号化されたWireGuardベースのトンネルを使用して、ポートフォワーディングや複雑なファイアウォールルールの必要性を排除します。
  • 📦 Tailscale公式のDockerイメージは、Docker HubおよびGitHubコンテナレジストリで利用可能です。
  • 🔑 Tailscaleにコンテナを追加するための主な方法として、認証キーとOAuthクライアントがあります。
  • 🔧 APIアクセス、有効期限、ライフスパン、タギングなど、認証キーとOAuthクライアントの違いについて説明します。
  • 🛠 Docker Composeを使用してTailscaleコンテナを設定する具体的な手順を提供します。
  • 🌍 Tailscaleの`serve`と`funnel`を使用して、Tailnetのアプリケーションを公開インターネットに直接公開する方法を解説します。
  • 📚 コンテナをTailnetに追加するプロセス、セキュリティ考慮事項、およびTailscaleとDockerを利用する利点を網羅したリソースを提供します。

Q & A

  • Why might you want to run Tailscale in a container?

    -Some reasons are: to control access via ACLs, replace reverse proxies, and access services across different environments like on-prem and cloud.

  • What are the two main methods for adding a container to your Tailnet?

    -The two main methods are using an auth key and using an OAuth client.

  • How do auth keys and OAuth clients differ in terms of API access?

    -An auth key grants full API access, while an OAuth client limits API access through scoping.

  • What is the maximum lifespan of an auth key?

    -An auth key has a maximum lifespan of 90 days.

  • How does node ownership work with auth keys versus OAuth clients?

    -With an auth key, the node is owned by the user who generated the key. With OAuth, the node is owned by the tag assigned to the client secret.

  • Why is specifying a state directory important when adding a Tailscale container?

    -The state directory persists data about the node's connection to the Tailnet, so the node doesn't have to re-authenticate if the container restarts.

  • What does the ts_serve_config variable do?

    -It allows you to provide a configuration file that tells web requests where to go, acting like a reverse proxy.

  • What is the benefit of using Tailscale Funnel?

    -Funnel allows exposing a Tailscale application to friends/family outside your Tailnet without further configuration.

  • How does Tailscale obtain HTTPS certificates for containers?

    -It requests certificates automatically from Let's Encrypt for domains pointed to containers.

  • Why is it important to mount a directory instead of a file for ts_serve_config?

    -Mounting the file directly breaks notifications when the file changes. Mounting a directory avoids this issue.

Outlines

00:00

📦 TailscaleとDockerの統合について

現代では、コンテナを本番環境で稼働させることが一般的になりました。このビデオでは、TailscaleとDockerを組み合わせる方法に焦点を当て、Tailscaleのコンテナを「tailnet」に追加する方法、なぜTailscaleをコンテナで実行するのか、そしてTailscaleのserveとfunnelを使用してtailnet上のアプリケーションをインターネットに公開する方法について説明します。特に、Tailscaleを使用することで、ACLを通じてアクセスを管理し、リバースプロキシを置き換え、他のサービスに簡単にアクセスできる利点があります。また、Tailscaleの公式Dockerイメージの使用方法と、環境変数を通じてコンテナをカスタマイズする方法についても触れています。

05:02

🔑 認証方法とその管理

Tailscaleネットワークにコンテナを追加する主要な方法として、認証キーとOAuthクライアントの2つが紹介されます。認証キーはAPIレベルのアクセスを提供し、一方OAuthクライアントはアクセス範囲を限定します。このパートでは、これらの認証方法の違い、特にAPIアクセス、有効期限、ライフスパン、タギングに関する細かなニュアンスについて説明しています。さらに、Visual Studio CodeのTailscale拡張機能を使用してUbuntu仮想マシンにDocker Composeファイルを設定し、認証キーを使用してコンテナをtailnetに追加するプロセスを実演します。

10:07

🔒 OAuthクライアントと認証キーの比較

OAuthクライアントと認証キーの具体的な使用例と設定方法について解説します。具体的には、OAuthクライアントを使用してコンテナをTailnetに追加する手順、OAuthクライアントが認証キーとどう異なるか、そしてOAuthクライアントを使用する際のメリット、特に有効期限がないことや、より具体的なスコープ管理が可能である点について詳しく説明しています。また、Docker Composeファイルを用いた実装例を通して、実際の設定手順も紹介しています。

15:09

🐋 DockerとTailscaleのネットワーキング

Dockerコンテナのネットワーク設定とTailscaleの統合方法について詳細に説明しています。Linuxカーネルのネームスペース、特にネットワークネームスペースの概念を紹介し、コンテナがどのように独自のネットワークコンテキストを持つかを説明します。また、Docker Composeファイルを使用して、NginxウェブサーバーとTailscaleコンテナを同じネットワークネームスペースにグループ化する方法を実演し、この設定がどのようにネットワークの隔離を解除し、コンテナ間の直接通信を可能にするかを解説します。

20:13

🌐 TailscaleのServeとFunnelの使用

TailscaleのServeとFunnel機能を利用して、Dockerコンテナで実行されるアプリケーションを内部ネットワークやインターネットに公開する方法について説明します。特に、Tailscale Serveを使用して、特定のポートでリッスンしているアプリケーションをTailnet内で安全に公開するプロセスと、Tail Scale Funnelを使用してアプリケーションをインターネット上で公開する設定の変更方法について解説します。また、Tailscaleで提供されるHTTPS証明書を使用して通信を保護する方法にも触れています。

25:19

📜 自己ホスト型レシピアプリのTailscale統合

自己ホスト型レシピ管理アプリ「Meely」をTailscaleネットワークに統合し、外部からのアクセスを管理する方法について説明します。TailscaleのServe設定を利用して、アプリケーションをTailnet内で安全に公開する手順と、HTTPS証明書を使ったセキュアな通信の確立方法について解説しています。また、設定ファイルの動的置換機能や、アプリケーションを公開する際の考慮事項についても触れています。

Mindmap

Keywords

💡container

A container is a standardized unit of software that packages up code and dependencies so the application runs quickly and reliably from one computing environment to another. Containers are a core concept in the video, which focuses on running Tailscale network software within Docker containers.

💡Tailscale

Tailscale is a Zero Trust networking platform that makes networks secure, reliable, and easy to use. It is a core focus of the video, which covers connecting Docker containers to a Tailscale network (tailnet).

💡tailnet

A tailnet refers to a Tailscale network - a private network created by Tailscale devices and containers, communicating over encrypted Wireguard tunnels. The video talks about adding containers to your tailnet.

💡Wireguard

Wireguard is an extremely simple yet fast and modern VPN protocol that uses state-of-the-art cryptography. Tailscale tunnels use Wireguard under the hood to provide encrypted communications between devices on a tailnet.

💡ACL

ACL refers to an access control list - a list of permissions attached to an object defining who can access it and what operations they can perform. The video mentions controlling container access via ACLs on a tailnet.

💡magic DNS

Magic DNS is a Tailscale feature that automatically assigns DNS names to devices on your tailnet, allowing them to easily communicate without knowing IP addresses. The video shows accessing containers via their magic DNS names.

💡authentication

Authentication refers to verifying the identity of a client or computing entity accessing a private network like a tailnet. The video covers two Tailscale authentication methods - auth keys and OAuth clients.

💡namespace

In Linux, a namespace wraps a global system resource in an abstraction that makes it appear independent from other namespaces. The video examines using namespaces to connect Docker container networking.

💡Tailscale Serve

Tailscale Serve is a simple(-ish) dynamic reverse proxy server for mapping public DNS hostnames (like myapp.example.com) to services running on your tailnet. The video uses it to expose a containerized web app.

💡Tailscale Funnel

Tailscale Funnel enables exposing servers on your tailnet to the public internet without requiring public IPs or opening firewall ports. The video configures funnel access for a containerized web app.

Highlights

Running a container in production has shifted from being unusual to a common practice over the past decade.

Tailscale and Docker integration allows containers to be added directly to a Tailnet, offering access control and replacing the need for reverse proxies.

Tailscale's encrypted WireGuard based tunnels simplify network configuration by eliminating port forwarding and dynamic DNS setup.

Tailscale provides an official Docker image, simplifying deployment and configuration through environment variables.

Two main programmatic methods for adding a container to your Tailnet: Auth keys and OAuth clients, each with unique advantages.

Auth keys offer full API level access for easy integration but have a maximum lifespan of 90 days, requiring consideration for security and maintenance.

OAuth clients provide scoped API access, allowing for more granular control and potentially indefinite lifespan without manual intervention.

Using Tailscale allows for direct, secure connections between services in different locations, such as linking on-premises hardware to cloud resources cost-effectively.

Tailscale's Docker setup supports advanced network configurations without requiring container privileges, enhancing security.

Exploration of Docker networking and Linux kernel namespaces reveals how Tailscale facilitates secure and isolated container communication.

Tailscale Serve and Funnel features enable public internet exposure of applications on your Tailnet with minimal configuration.

Implementing a self-hosted recipe manager on Tailnet demonstrates practical application of Tailscale's networking capabilities.

Tailscale's configuration allows for HTTPS traffic proxying and automatic certificate management, simplifying secure web service deployment.

The guide includes detailed walkthroughs for setting up containers with Tailscale using Visual Studio Code extensions and Docker Compose.

The video concludes with encouragement for community feedback to improve Tailscale and Docker integrations, highlighting the value of user experience in product development.

Transcripts

play00:00

About 10 years ago you were pretty unusual if you were running a container in production,

play00:04

whereas these days you're unusual if you're not. In today's video we're going to talk about

play00:09

tailscale and docker, we're going to cover how to add a container to your tailnet, why you might

play00:14

want to consider running tailscale in a container in the first place, as well as how to use tailscale

play00:19

serve and funnel to expose those tailscale applications from your tailnet directly to the

play00:25

public internet. Today's video will be quite a long one so i've put chapter markers down there

play00:29

for you to skip around and find the bit of the video that you need, so with that let's get started.

play00:34

Well the first question that probably comes to mind is why would i even want to put tailscale

play00:40

in a container in the first place? Well by putting a container directly onto your tailnet,

play00:46

that's our term for a tailscale network by the way, you can not only control access via ACLs

play00:52

but you can also replace reverse proxies. Yes for those of you that have been waiting to learn

play00:57

reverse proxies for long enough i've got good news you don't have to learn them ever you can

play01:01

just completely skip that step and you can also access any other service on your tailnet from

play01:08

these containers as well. So you can have something running in your basement in your house connected

play01:12

to something in the cloud, take for example a GPU workload some AI workload or something like that,

play01:19

you've got something running in AWS or GCP and you don't want to pay their GPU prices but you've got

play01:25

a GPU sat in your gaming computer like right here and you want a way to connect the two together

play01:30

well using tailscale you can do just that, think of the possibilities and this all happens through

play01:36

tailscale's encrypted wire guard based tunnels, you don't have to mess around with port forwarding or

play01:42

complex firewall rules or dynamic DNS or any of that it all becomes a thing of the past.

play01:50

tailscale provides an official docker image that you can find over on docker hub as well as github

play01:56

container registry, this exposes several parameters through what are called environment variables

play02:03

there'll be a full list of those variables exposed by the container down below. There are two primary

play02:08

methods for adding a container to your tailnet, well three if we include logging into the container

play02:14

manually, running tailscale up, copying the resulting printout, logging into the browser manually

play02:20

and redoing that every single time you bring up a container. The two methods we're going to cover

play02:24

today are both what are called programmatic methods so these are ones that you can repeatedly do

play02:29

without any manual intervention. The first option is called an auth key and the second is called an

play02:36

oauth client and as for which one is right for you, well it kind of depends i'm afraid, it's just one

play02:43

of those classic situations where it really does depend on what you're going to be doing with it.

play02:48

So let's dig into some of the differences between the two, both of these methods actually support

play02:53

most of the same things there's just some nuances that you've got to be aware of when choosing

play02:58

between the two. So let's first of all take a look at api access, an auth key grants full api

play03:06

level access to any client that authenticates using it, this is in contrast to an oauth client

play03:12

which limits api access via scoping. So this for example means if i have an oauth client secret

play03:18

that is scoped to only allow people to modify the dns or scoped to only allow people to read and

play03:25

write devices on my tailnet or only scope to read audit logs that's all they can do or any

play03:31

combination of those different things as well by the way. Whereas with an auth key because it

play03:36

grants full api access to anybody that has that secret they can do whatever they want and so if

play03:43

auditors are in your regular vernacular it's likely that this alone will be enough of a feature for you

play03:49

that rules out auth keys in favor of oauth clients. The next thing to look at is expiry and lifespan.

play03:56

An auth key has a maximum lifespan of 90 days, now a lot of people think that this means that

play04:04

anything that's authenticated using that auth key also automatically expires after 90 days,

play04:10

this isn't the case. It's worth bearing in mind that auth keys are separate from the node keys,

play04:16

so under the hood what happens when you authenticate a client to your tailnet

play04:20

is node keys are generated, so this is underneath it's the wire guard key exchange magic that makes

play04:25

tail scale work, that is a completely separate expiry process from the authentication token

play04:32

that you use to initiate that process. A lot of people use oauth clients simply because they

play04:38

never expire, now in practice what this means is you're going to have to come up with some way to

play04:41

regularly rotate those tokens so that any security posture that you have or any risk profiles you

play04:47

have are met or if you just simply want a set it and forget it type solution and an oauth client

play04:52

might be right for you but there are plenty of situations where you might want to give someone

play04:56

temporary access to add something to your tailnet and then have that auth key automatically revoked.

play05:02

The next important difference is the tagging, so when you add a node to your tailnet it must be

play05:07

owned by somebody and when you use an auth key that identity by default at least with an auth key

play05:14

is assumed to be the person who clicked the generate auth key button in the UI. You can

play05:19

add tags to an auth key if you would like but with an oauth client that secret doesn't actually itself

play05:25

add the node to the tailnet it has to assume the identity of a tag owner instead. This is because

play05:31

a node on a tailnet must have an owner whether that's a user or a tag and when you use an auth

play05:37

key that node is added to your tailnet as the user who generated the key. With an oauth client though

play05:44

the node is owned by the tag assigned at secret creation time and this is why in our ACLs you will

play05:49

see tags listed as tag owners. The oauth client is assuming ownership of a specific resource on

play05:56

your tailnet using the tag so the tag ownership is assumed using the tag assigned to the client

play06:03

secret at the time of creation. So let's walk through adding a container to our tailnet using

play06:10

an auth key. I'm using the visual studio code tail scale extension here to access a Ubuntu

play06:17

virtual machine with docker pre-installed or docker compose ready to go and I have here a

play06:22

sample docker compose file there'll be a link to a github repo down below with all of these resources

play06:28

for you as well as the blog post that accompanies this video with all the materials explaining

play06:33

you know all the details behind this this file. So let's walk through the compose file that we've

play06:39

got here. So first of all on line five we have the tail scale image this is the official tail

play06:45

scale docker image from docker hub. Next up we have the container name so this refers to the

play06:50

name that docker gives the container on the Ubuntu host. Next up we have the host name now this is

play06:57

important because this is the name that tail scale will will add to this container to the tailnet

play07:03

with the name of so whenever we want to use magic dns to refer to this container we will use auth

play07:09

key hyphen test you can make this value whatever you like by the way it doesn't have to match the

play07:14

container name or the service name. Next up we're going to specify our environment variables so here

play07:20

we're specifying our auth key this is basically like a password remember so just you know treat

play07:26

it with care and then the next thing we have here is the state directory now the state directory is

play07:32

really important and it's coupled by the way with this line here of volume so var lib tail scale is

play07:37

the directory that the container is going to use to persist all of the state of you know that what

play07:43

state when i logged in and authenticated to the tailnet what was my wire guard key exchange

play07:49

underneath like all of that kind of stuff gets stored in this directory and the reason it's

play07:54

important is because if the container restarts or gets recreated or any other kind of major event in

play08:00

the container's lifespan the state gets persisted in this directory and so it doesn't have to

play08:06

re-authenticate to the tailnet every single time or get out of sync with what's going on. Next up

play08:11

we have the ton device this is a networking necessity and below it we have a couple of

play08:19

capabilities that we've added to the container as well so net admin and sys module these three or

play08:24

four lines here mean that we don't need to give this container a privileged status within the

play08:30

kernel so it's a much more secure way of doing it because we're being very explicit with the

play08:35

permissions that we're giving this container with these lines here and then restart unless stopped

play08:39

totally optional you don't need this bit unless you want to. Next up we have the actual web service

play08:44

that we're trying to run here now this is just a very basic nginx web container it's not even got

play08:49

a real website in it it's just we're just going to load the nginx test page on port 80 and then

play08:55

finally we have the network mode now this is the magic source that connects everything together

play09:00

and makes this whole thing work so network mode service colon ts auth key test this is a docker

play09:08

compose specific incantation so ts auth key test here actually refers to the name of the service

play09:15

up on line four up here now if you're doing this with docker run command for whatever reason

play09:20

you can actually replace the name of this with container and then the name of the container so

play09:25

if you know if this was uh ts one two three you would you would do ts one two three like this

play09:31

and those two things would have to match but i'm going to use service because i'm in the docker

play09:35

compose ecosystem so i'm quite happy with doing it like this now what i'm going to do here oh i

play09:41

should probably generate my auth key before i do compose up shouldn't i so let's jump across to

play09:45

our tailscale admin console and go up here to settings and then under settings we're going to

play09:51

want to jump into the keys section then we're going to click on generate auth key and then we're just

play09:57

going to give it a name the name can be whatever you like so i'm going to call this docker auth

play10:01

key test it can be whatever you like it's not important it's going to be a single use key so

play10:07

we're not going to do any reusability or anything like that reusable keys are they're fine but

play10:13

they're quite risky so if if you don't treat them with care and you have a key that lasts for 90

play10:19

days you've effectively given anybody that finds that string of characters root access to your

play10:24

tailnet you know they can add and remove devices there is no scoping remember for an auth key so

play10:31

anybody that has this key can do whatever they like to your tailnet programmatically so just

play10:37

bear that in mind and treat it with care next up we've got the expiration i'm going to set this to

play10:41

90 days now there is a bit of a misconception around expiration a lot of folks seem to think

play10:46

that if the container gets to 90 days and you authenticated using an auth key that expires

play10:51

after 90 days that container is going to drop off your tailnet and completely need a new auth key

play10:57

bang on 90 days it's not quite the case the keys we use to authenticate the node to the tailnet

play11:03

are different from the wire guard keys that get exchanged behind the scenes later on those keys

play11:08

are good for about four months or so and you can disable key expiry if you want to in the

play11:13

tailscare admin console as well so theoretically you could use an auth key with a one day expiry

play11:20

set your key expiry to disabled and that container would stay on a tailnet until the end of time

play11:26

another setting that's worth exploring is ephemeral so if you're doing something with cicd

play11:31

continuous integration maybe you have a github action that wants to talk to something off site

play11:36

for example it's not part of the github ecosystem those jobs are typically short-lived so you don't

play11:41

necessarily want those containers on your tailnet forever so an ephemeral node will automatically

play11:48

remove itself after it goes offline i don't want this in this case but it can be very useful in

play11:53

certain situations and now we come to tags we talked about tags a little bit earlier but this

play11:59

is where you would set a tag as it pertains to a specific auth key and you can set multiple tags

play12:04

here if you want to i don't really want any tags on this node i want this node to be authenticated

play12:09

as my user so i'm just going to leave this empty i'm going to click on generate key and then i'm

play12:14

going to copy this to my clipboard go back to my docker compose file and just enter the value that

play12:20

we had on our clipboard click save now i can do my docker compose up i'm going to create a couple

play12:28

of containers you can see there's a whole bunch of stuff scrolling by here the auth key has appeared

play12:34

the containers appeared in my tailnet you can see that in the vs code dashboard just there

play12:38

if i go back to my admin console notice that my single use key has actually automatically

play12:45

invalidated itself remember it was a single use key so it automatically puts itself in the recently

play12:51

invalidated auth keys section down here and then on my machines page we can see that if i look at

play12:58

auth key test i now have a container that's in here so if i go to http auth key hyphen test which

play13:05

is the dns name that we set in our host name remember just here auth key hyphen test we can

play13:11

actually refer to this container by its host name by its magic dns name and resolve the url that's

play13:17

running the web page now nginx is running on port 80 underneath so it's just a plain http request

play13:23

and we're actually connecting we'll dig into the specifics a little bit later on but we're

play13:27

actually connecting to port 80 of this container which is which has attached itself to the interface

play13:33

of your tail scale container but that's how we get started with an auth key let's now look at oauth

play13:39

clients let's start by looking at the differences between an oauth client and an auth key so i've

play13:46

got a second docker compose yaml file here which you'll notice is pretty similar to the docker

play13:52

compose on the left so on the left we have the auth key and on the right we have the oauth client

play13:57

you'll notice that we're actually even using the same environment variable okay we've added an

play14:02

extra one with ts extra args this is where we specify the container tag that the auth

play14:09

client uses the oauth client uses when it brings up the container so when the container starts

play14:14

it's running something called container boot this does a tail scale up command under the scenes

play14:20

and it recognizes that we've provided an oauth client secret it then uses that secret to generate

play14:26

an auth key under the covers with the specific tag that we've associated here because if you remember

play14:33

when you add a node to a tail net it has to be owned by somebody and tags and users are all sort

play14:39

of in this melange of ownership and so when we add the node with the tag container we're

play14:46

authenticating that node as if it belongs to that specific tag owner generating an oauth client

play14:52

secret's quite straightforward so again we're going to jump back to our tailscale admin console

play14:57

go to settings go to oauth clients over here on the left click generate oauth client i'm going

play15:03

to select devices right and notice that the read permission is automatically set i'm just going to

play15:09

write test one two three for the description it's totally optional by the way and under tags here

play15:14

tag container i'll just show you real quick where i set that up in my acl so i just have a tag

play15:20

container here which is owned by the group auto group admin remember there'll be a link down below

play15:26

to all of the details like there'll be a git repository with my acl file just so you can copy

play15:31

and paste if you want to as well as the linked blog post so if we go back to oauth clients over

play15:37

here we've got the tag container and then we just click generate client the client id isn't

play15:42

particularly important to us here but the client secret treat it like a password once again same as

play15:47

with an auth key i'm going to go back to vs code and just copy in my value here i'm going to click

play15:54

save and then just going to change into the correct directory and then do a docker compose up

play16:01

this is going to add a second container to my tailnet now using the name ts oauth test actually

play16:08

not quite in line seven it would just be oauth test so if we go over here to my machines page we

play16:15

can see that oauth test is there also note that expiry is disabled nodes that are added to your

play16:22

tailnet using an oauth client do not expire so that's that's a big difference between an auth key

play16:29

and an oauth client authenticated node and by default nodes are also added as ephemeral so in

play16:36

order to make a node non-ephemeral we'll have to stop the container remove the state directory that

play16:42

we have so uh ts oauth test by the way that is this directory here remember where the state

play16:49

gets stored when the node authenticates to the tailnet we'll remove that directory

play16:52

as sudo and then we need to just add on to the end of our auth key just here ephemeral equals false

play17:03

also need to make sure it's deleted from your tailnet as well in your admin console

play17:08

then bring the save the compose file and then bring the node back up again with docker compose up and

play17:14

if we go back to our admin console we will see that the oauth node appears expiry is still disabled

play17:20

but the node is no longer ephemeral so it's a really minor difference but just being able to

play17:25

add that ephemeral equals false on the end means the nodes have a lot more permanence on your

play17:29

tailnet so depending on your use case that may or may not be useful for you and again just to verify

play17:36

what's going on here i want to do instead of um doing it in the web browser this time i want to do

play17:42

it in a terminal session so i'm going to do a curl http oauth test and you saw the web request flow

play17:49

by in the terminal underneath and then we get the the nginx landing page as well so there you go we've

play17:54

now added a node to our tailnet using an auth key and also an oauth client now i promised you a bit

play18:02

of a deeper dive as to how the docker networking is actually connecting everything together under

play18:06

the hood so we're going to dive a little bit into linux kernel namespacing i promise it's not as

play18:12

intimidating as it sounds so when we create a container what we're doing effectively is creating

play18:18

a new namespace in the linux kernel with a whole bunch of different things that control where

play18:23

different resources live one of those namespaces that we create is a networking namespace so each

play18:30

container has its own networking context when we add network mode to a specific container we can

play18:38

actually group two containers together under the same namespace so let me show you that in action

play18:44

so what i have over here is a docker compose file it's basically the same as the one we had before

play18:49

it's got an nginx web server in it as well as the tailscale container now notice down here on line

play18:55

22 that i've actually commented out network mode so when we bring these containers up what's actually

play19:01

going to happen under the hood is it's going to create two networking namespaces in the linux

play19:07

kernel and we can just check that these two containers are both running with a docker ps

play19:12

hyphen a let's suppose i want to find which ports this nginx container is listening on

play19:18

internally well the first thing i might want to do is grab a docker exec docker exec spawns a

play19:23

process inside the container a shell process and lets us attach to that assuming that that binary

play19:29

exists within the container so what i want to do here is do a docker exec then the name of the

play19:35

container in question which is nginx PID test one and then i'm going to do netstat minus ton alp

play19:44

ah we run into our first issue and this is because images like nginx are designed to be web servers

play19:51

they're not designed to be diagnostic tools and so a lot of containers strip out unnecessary binaries

play19:57

but using namespaces we can actually hop into the networking namespace or hop into the context

play20:04

of the networking namespace using a tool called nsenter now this tool it's really pretty cool

play20:12

actually but what we can do is we can provide a bunch of different contexts in here for example

play20:18

we're going to use the -n for net context and we need to grab the PID or the process id

play20:24

for the docker container that we want to inspect so to do that we're going to use the docker inspect

play20:30

command and i'm going to look for the PID of the container nginx PID test one which we can see here

play20:36

is 53.1.6.6 then i'm going to use nsenter i'm going to run it as sudo because i need root

play20:43

privileges to change into a different namespace then i'm going to give it the target of 53.1.6.6

play20:49

because we're targeting the process id that owns or is part of that namespace that owns the network

play20:54

namespace and then i'm going to run the command in the networking context netstat ton alp which

play21:02

if you recall is exactly the same command i tried to run before with the docker exec that didn't

play21:07

work and what we can see here is that within our nginx container that we've got a couple of commands

play21:12

running on ipv4 port 80 here as well as ipv6 on port 80 as well so let's just stop and think about

play21:19

what happened for a second i'm on my linux host and i changed into a different namespace using

play21:26

namespace enter as the command and then i ran an arbitrary command as if i was in the context of

play21:33

the container and so when we talk about containers that's what we're talking about is different

play21:37

context different processes sliced up within the side the linux kernel in memory space but where

play21:43

things start to get really interesting is when we start trying to stack the containers together

play21:48

using network mode so over here in my docker compose file you can see that network mode is

play21:53

currently commented out what this means is that we're going to be creating two different network

play21:59

namespaces for these different containers one each so let's do a docker compose up and then use a

play22:06

couple of commands to examine what's going on with the namespaces underneath so i've got an all-in-one

play22:12

command here which is going to use ns enter once more and it's going to grab the PID you see it's

play22:17

going to use docker inspector as we did before to inspect the namespace for ts nginx test this is the

play22:24

tail scale container not the nginx web server and you can see that my namespace here the network

play22:32

namespace for just this specific container only contains things pertaining to tail scale d so that

play22:39

must mean that this is the tail scale container and if i do the same thing again here and i'm going

play22:43

to load instead of ts nginx test i'm going to investigate nginx PID test one we can see that

play22:51

we've got nginx listening in its namespace inside its container so these two containers right now

play22:58

both have separate namespaces within the linux kernel with regards to networking another way we

play23:04

can check this is to use docker's built-in network tools so if we do docker network ls this is going

play23:10

to show us all of the different docker networks that get created and by default docker compose

play23:16

creates a new network per stack so what we have here is a stack called 0 3 nginx example default

play23:23

so if i do a docker network inspect of that network what we can see and i'll just pipe it

play23:30

through to jq so it looks pretty and what we can see here is there are two containers currently on

play23:36

that docker network they've both got their own ip address from the docker bridge so this one's 0 3

play23:40

this one's 0 2 you can see the different names of the containers here as well and what this shows

play23:46

us is that these two containers are currently operating as we would expect as isolated units

play23:53

now let's go ahead and uncomment network mode i'm going to recreate these containers and just

play24:00

watch what happens i'm going to run the same command again docker network inspect and suddenly

play24:05

there is only one container showing up we can see it's ts nginx test well this is exactly what we'd

play24:12

expect because the nginx PID test one container we've given it access to the network namespace

play24:19

of this ts nginx test container so actually what's happened is we've merged them together

play24:24

and we can prove this by looking inside the container's networking namespace so let's first

play24:29

examine the nginx PID test one namespace and see what's going on and then let's examine the actual

play24:35

tail scale container upstream of that container what we've done is we've merged these two

play24:41

namespaces together in the kernel and you can see we've got the same processes running in both places

play24:48

so we've taken those two separate namespaces and merged them together by using the network mode in

play24:54

the docker compose file so let's put all of this new knowledge together and put a self-hosted

play25:01

recipe manager onto our tail net i've got an app here called meely and another docker compose file

play25:06

which looks again suspiciously similar to the ones that we've been looking at throughout this video

play25:12

we've got our client auth key here we've got our tags there's a new one here called ts serve config

play25:19

which will come on to throughout this section and then we've mounted a couple of extra volumes we've

play25:24

got the config directory which is where our tail scale serve configuration lives and then the actual

play25:29

application itself so we've got meely as an application here using docker volumes to persist

play25:35

the data so let's bring this container up and it'll take a couple of seconds to start it uses

play25:41

gu unicorn under the hood and we can wait for it to come up on port 9000 this is important because

play25:47

what we were doing before with nginx was on port 80 so we didn't have to do anything to make web

play25:52

requests work we just they just worked because it was on port 80 this one though is on port 9000 so

play25:58

if i go across here and go to meely port 9000 we can see that's fine and it works just fine but

play26:05

it's on plain http there's no you know tls certificate or anything like that and i don't

play26:12

really want to have to remember port numbers and so this is where tail scale serve and in a minute

play26:18

tail scale funnel come into the equation tail scale serve lets us provide a configuration file

play26:24

that will tell basically a bit like a reverse proxy that will tell web requests where to go and

play26:30

so let's take a look at one i've got over here a sample json file we can see at the top these first

play26:36

few lines refer to proxying TCP/HTTPS traffic we we want this this is a good thing next up we've

play26:44

got the web section this is what is basically acting like a reverse proxy so when i receive

play26:50

a request of meely dot velociraptor hyphen noodlefish dot ts dot net on port 443 proxy

play26:59

that request under the hood to localhost port 9000 now remember that this is actually running inside

play27:07

the tail scale container but because we've done network mode and added that other container into

play27:12

the namespace of the tail scale container in the linux kernel it's actually listening on port 9000

play27:19

inside the tail scale container so that's how the localhost part here works and then finally

play27:25

i've got allow funnel set to false for right now funnel would allow me to expose this application

play27:30

across the internet with no further configuration required other than just changing this value to

play27:35

true so if you had friends and family in another country that weren't on your tail net for some

play27:40

reason changing this value from false to true would let them access this application as if they

play27:46

were on your tail net now remember that means it's also exposed to anybody on the internet so use this

play27:52

option with care the upshot of this configuration file is i can now go to meely dot velociraptor

play27:59

hyphen noodlefish dot ts dot net and i don't have to worry about typing import numbers or anything

play28:06

like that what you might actually notice in the terminal window at the bottom is that when i make

play28:11

this request it was actually requesting a certificate from let's encrypt so this website

play28:17

is exposed only on my tail net right now it's not on the public internet but it's backed with

play28:23

a proper tls certificate from let's encrypt so if i look at the certificate here you can see

play28:28

it's a let's encrypt certificate and and the whole purpose behind tls really is to verify that you

play28:34

are actually talking to the web server that you think you're talking to it verifies the ownership

play28:40

of that domain and so let's get logged in and have a little poke around and see what's what

play28:44

just type in my password here and oh yeah i feel like i'm in the mood to bake some bread don't you

play28:52

the easiest loaf you'll ever bake well that's fantastic we've got a self-hosted recipe app

play28:57

on our tailnet but if you noticed if you were keen-eyed hard coding things into a configuration

play29:03

file is never going to be fun in the long term to manage at least and so what we've actually done is

play29:09

we've implemented under the hood a way to substitute the values in this file using this

play29:15

ts cert domain variable just here so if you put this into your configuration file when tail scale

play29:22

starts up it will actually substitute the value of this variable for the domain of your container's

play29:29

tailnet and we can verify this by hopping over to the container host itself so if i do a docker psa

play29:36

and then look for a docker exec command in here so docker exec 0 3 b 1 this is going to give me a

play29:43

shell inside the tail scale container itself what i can do is do a tail scale serve status this is

play29:50

going to show us that whenever we receive a request on the root of the domain so the slash is the root

play29:56

of the domain it's going to proxy localhost port 9000 now if you're not a fan of typing out

play30:02

configuration files yourself like i said you can do a dash json on the end and it will actually

play30:07

print out the whole file for you in exactly the right format if you need it to feed into the ts

play30:13

serve config environment variable that i showed you earlier it's really important for this

play30:18

configuration file that you mount a directory and not the specific file itself if you mount the file

play30:24

directly it breaks the way that fs notify tells docker that the file has changed we haven't done

play30:31

that here we've just mounted a directory so we're okay but just notice what happens when i change

play30:35

false to true for example it creates a whole bunch of configuration on the tail scale side to now

play30:41

expose this application using tail scale funnel so anybody on the public internet could now access

play30:47

this application et voila we have a self-hosted recipes app running natively on our tail net with

play30:54

a valid https certificate available both internally to tail net devices only and if you turned it on

play31:02

externally to other devices using tail scale funnel this approach scales to multiple containers

play31:08

and different operating systems as well not only that but you could also let's just think about it

play31:13

for a second you could run one container in your basement you could run another one in digital

play31:18

ocean for example and then have another one at your friend's house and have all of these

play31:23

different devices all talking to each other natively encrypted over tail scale if you're

play31:29

already a regular user of tailscale on docker let us know down in the comments how you're using it

play31:34

so we can make it even better in the future don't forget you can find all of the companion resources

play31:38

for this video in the description box down below so that's the blog post the git repo various

play31:44

different kbase articles they're all down there for you to use so until next time thank you so

play31:49

much for watching i've been alex from tailscale