(I believe this will all work the same with tailscale, but I was using headscale. )
I want to host a gitlab instance both inside and outside a private headscale network. I'm intending that the internal gitlab will support git protocol (over ssh) but that won't be available for people outside. I do want to expose gitlab to the outside world in read-only format.
I played with multiple ways of doing this and found a simple solution that uses the same URL both inside and out.
First, create a user in the headscale network called "p". All services you want to be public should be attached to that user.
Create a new machine and add that to the p user. I call that machine gitlab because I want it to host gitlab. I'm running gitlab inside an Ubuntu VM.
If I'm inside the headscale network, then I automatically can go to gitlab.p.katarismo.com. That's the magic of magicDNS: I didn't edit a nameserver at all, it just comes automatically.
Now, let's expose that to the outside world.
You will need an external server. I setup a Vultr server for $4/mo. Inside the vultr server, install nginx.
Add that external server to the headscale network. You can give it any name you want. Once it has been added, make sure that you can access the internal service you need. MagicDNS should make it instantly accessible over the internal domain name as soon as the machine is on the network.
You will need SSL certs. I used letsencrypt. The great thing is that you can do all this with wildcard certs and add as many services as you want with the same certs! All will be under the *.p.hostname.com wildcard, so you only need to grab that one time.
This command gets that:
certbot certonly --preferred-challenge dns --manual -d *.p.katarismo.com
That is done with the DNS challenge for Letsencrypt, which requires you to set a DNS TXT record, and it will prompt you for that.
Then, create an nginx server in /etc/nginx/sites-available (and symlink it to /etc/nginx/sites-enabled) like this:
$ cat /etc/nginx/sites-enabled/gitlab.p.katarismo.com map $http_upgrade $connection_upgrade { default keep-alive; 'websocket' upgrade; '' close; } server { listen 80 default\_server; listen [::]:80 default_server; listen 443 ssl default_server; listen [::]:443 ssl default_server; root /var/www/html; ssl_certificate /etc/letsencrypt/live/p.katarismo.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/p.katarismo.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; # Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html; server_name gitlab.p.katarismo.com; proxy_ssl_verify off; location / { proxy_pass https://gitlab.p.katarismo.com/; proxy_http\_version 1.1; } }
That will proxy to the backend inside the headscale network.
You will need to set DNS for gitlab.p.katarismo.com inside your DNS provider. But, the cool thing is that your internal network will see the headscale IP and not the public IP. If a user comes to that site and they are not inside the headscale network, they get the public IP and they go to that frontend. If they are inside the headscale network, they get the private internal machine with SSH port access. Adjust your TTLs appropriately if you think people will be roaming inside and outside. The only issue would be that gitlab support for ssh will only work inside the network, so if their machine has the wrong IP address it will not be able to connect to SSH. You could easily fix this on the client side by using an ssh configuration so only SSH clients (like git over ssh) would use the internal IP address, but, of course, you would need to tell your users to manually set that up.
What's great is that the URLs are all the same. You can git clone the same URLs regardless of whether you are inside or outside. If this were a service I built, I could of course do a bunch of special things by detecting the client IP address and offering special access, etc.
But, I love that I only need to generate one cert for all external services as long as they sit on the p.katarismo.com domain. That's so cool!