Synology HTTPS Docker Apps with Tailscale
I’ve always wanted a way to run various docker apps with actual HTTPS certificates internally. Playing with DNS zones and reverse proxies can work well enough, but can be complex. If you are already using Tailscale and are a bit familiar with Docker, there is a way to cheat.
I came across this post/video from Alex Kretzschmar of Tailscale and the Self-Hosted podcast that shows you how this sorcery works. Only difference for me was that I run all my docker containers on my Synology NAS, so the instructions didn’t quite add up as all the containers are hosted from the same IP with different port numbers. After while keeping track of the ports/logins can be a pain.
To understand how it all works with the Docker network modes and service linking, Alex goes into detail in the link above for the curious. Well worth the read and view of his video.
For this post I am just going to go through an example of how to set up an app called Sterling PDF using Container Manager on Synology.
As Alex notes - you’ll need to decide between Auth keys and OAuth clients for getting the Tailscale container authenticated to your tailnet. I will be using OAuth clients as they are bit more set and forget, but your threat model may be different. image from Tailscale blog
One thing to note about this type of setup is it’s not ideal for every situation or application. You are relying on access to your tailnet for access to the application. If another device not connected to your tailnet needs access to that app, you’ll have to add them. Also, an application like PiAlert needs access to your local network to notify you when devices connect. Using this setup with that app will by default not allow that access to that network and you won’t see any devices apart from the gateway. There might be a good way to configure that access, but that is out of scope for this.
Some prerequisites to make sure you are using for the OAuth client setup are:
- Create/uncomment out your Access Controls section in your Tailscale admin portal so that the container can authenticate.
// Define the tags which can be applied to devices and by which users. "tagOwners": { "tag:container": ["autogroup:admin"], },
- Turn on MagicDNS in your DNS section of your Tailscale admin portal.
- Enable HTTPS in the same DNS section.
- (Optionally) - rename you tailnet if you want a more easily typed domain name to use.
- Generate an OAuth client under Settings —> OAuth clients with at least “Devices - Write” permissions and set the tag as you specified in your ACL above.
I’ll be using Stirling PDF as an example of taking my previous container that was running on http://192.168.1.50:8080. It’s a pretty cool app that lets you do all sorts of stuff with PDFs if you have that need. When we’re done it will run on https://stirling-pdf.penguin-lion.ts.net (I made the domain name up - but play around with the tailnet name generator on the site).
On your Synology, make sure you have the folder structure setup like the instructions suggest. Some of those are optional, but won’t hurt to create them for now.
For the Tailscale folder structure, I’m using the same naming method Alex used in his post with “ts-“ and the name of the other docker container folder for the app we’re setting up.
In the above “config” folder for tailscale, you’ll want to create a stirling-pdf.json file like below. This is what will help the HTTPS part work for us.
~Note:~ When I tried using Alex’s example json from his post it had an AllowFunnel: false
section which should turn off Tailscale Funnel service (expose the app to the internet), but that didn’t work for me in that it said funnel was on, so I just took out that section.
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"${TS_CERT_DOMAIN}:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:8080"
}
}
}
}
}
Here is the whole docker-compase.yml
file when everything is done. I’ll go through and explain a few items. Note that Synology default is “volume1” for the local paths.
version: '3.3'
services:
ts-stirling-pdf:
image: tailscale/tailscale:latest
container_name: ts-stirling-pdf
hostname: stirling-pdf
environment:
- TS_AUTHKEY=tskey-client-xyzclientid-xyz123secret?ephemeral=false
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/stirling-pdf.json
- TS_EXTRA_ARGS=--advertise-tags=tag:container
volumes:
- /volume2/docker/ts-stirling-pdf/state:/var/lib/tailscale
- /volume2/docker/ts-stirling-pdf/config:/config
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stopped
stirling-pdf:
image: frooodle/s-pdf:latest
container_name: stirling-pdf
network_mode: service:ts-stirling-pdf
depends_on:
- ts-stirling-pdf
volumes:
- /volume2/docker/stirling-pdf/data:/usr/share/tessdata
- /volume2/docker/stirling-pdf/config:/configs
- /volume2/docker/stirling-pdf/customFiles:/customFiles/
- /volume2/docker/stirling-pdf/logs:/logs/
environment:
- DOCKER_ENABLE_SECURITY=false
restart: unless-stopped
- There are two containers/services here: stirling-pdf and ts-stirling-pdf. The stirling-pdf container will depend on the ts-stirling-pdf container for its network (tailscale). You’ll notice that second container name is the same as the host name of the first container.
- The
?ephemeral=false
part at the end of the OAuth client string will allow the device to persist on the tailnet. - That
- TS_SERVE_CONFIG=/config/stirling-pdf.json
is the file I mentioned above. - The
- TS_EXTRA_ARGS=--advertise-tags=tag:container
line is the container tag that the OAuth client will use to authenticate. - There are other items that you might need explanations for, but those are either personal preferences (
restart: unless-stopped
), better described in Alex’s post, or easily found with a quick search.
Save this docker-compose.yml
file with your edits (namely the authkey and path names) and place it your stirling-pdf folder. When you step through the Synology Container Manager UI Project setup, it will pick the file up automatically. Go ahead and let it build at the end of the wizard and it will pull the images and setup the containers.
If you go into your Tailscale admin console under Machines you should see the stirling-pdf container. Now browse to https://stirling-pdf.(yourtailnetdomain).ts.net and you should now have an HTTPS docker app running on your tailnet!
As a bonus, here is another app called n8n to demonstrate adding a third (database) container to the mix. This one was setup with an Auth key so that is another difference. This is an app that really needs a domain to run properly for some features, so it’s a good use case for the Tailscale HTTPS method. Credit Marius for the basic layout of how to set this up.
version: "3.9"
services:
ts-n8n:
image: tailscale/tailscale:latest
container_name: ts-n8n
hostname: n8n
environment:
- TS_AUTHKEY=tskey-auth-xyz123-xyz123secret
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/n8n.json
volumes:
- /volume2/docker/ts-n8n/state:/var/lib/tailscale
- /volume2/docker/ts-n8n/config:/config
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stopped
db:
image: postgres
container_name: n8n-DB
network_mode: service:ts-n8n
mem_limit: 512m
cpu_shares: 768
security_opt:
- no-new-privileges:true
user: 1026:100
healthcheck:
test: ["CMD", "pg_isready", "-q", "-d", "n8n", "-U", "n8nuser"]
timeout: 45s
interval: 10s
retries: 10
volumes:
- /volume2/docker/n8n-new/db:/var/lib/postgresql/data:rw
environment:
TZ: America/Chicago
POSTGRES_DB: n8n
POSTGRES_USER: n8nuser
POSTGRES_PASSWORD: n8npass
restart: on-failure:5
depends_on:
- ts-n8n
n8n:
image: n8nio/n8n:latest
container_name: n8n
network_mode: service:ts-n8n
mem_limit: 1g
cpu_shares: 768
security_opt:
- no-new-privileges:true
volumes:
- /volume2/docker/n8n-new/data:/home/node/.n8n:rw
- /volume2/docker/n8n-new/files:/files:rw
environment:
N8N_HOST: n8n.<tailnet dns name>.ts.net
N8N_PORT: 5678
N8N_PROTOCOL: https
NODE_ENV: production
WEBHOOK_URL: https://n8n.<tailnet dns name>.ts.net
GENERIC_TIMEZONE: America/Chicago
TZ: America/Chicago
DB_TYPE: postgresdb
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_HOST: n8n
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_USER: n8nuser
DB_POSTGRESDB_PASSWORD: n8npass
restart: on-failure:5
depends_on:
db:
condition: service_healthy