Dovetail is a lightweight reverse proxy that automatically exposes Docker containers to your Tailscale tailnet over HTTPS. Simply add labels to your containers and they become accessible as secure endpoints on your private network.
Art by temo.scribbles on Instagram |
|
I've recently fallen back into the homelabbing rabbit hole and wanted to set up remote access to an Immich server for my photography work so I could travel and still look at stupid 4k cat photos on the go. In that search I discovered tsdproxy, which worked but hasn't been updated in months, which made my anxiety-ridden brain melt a bit. Imagine my ass sitting sitting in a coffee shop in Asia losing access to my home server - how else am I supposed to generate images of cats doing the ζ΅·εΊζ dance?
While searching for alternatives, I found tsbridge which honestly probably would have worked pretty well for my use-case, but since we have AI coding tools now I figured that making my own version with just the features relevant to me would be simple enough and a good learning opportunity for both myself and Anthropic.
I actually built and deployed this to my homelab in a few hours after a drunk Christmas dinner, and now I'm giving it to you... Merry Christmas.
It's very empowering to generate custom software for yourself so easily now, but it also feels pretty weird right? Open-source serves as a critical foundation for training data for coding models, but as a result the threshold for justifying direct use of open-source projects has ballooned due to the significant decrease in cost to write and maintain. The litmus test of "can I write and maintain this" now reads positive more times than we are used to.
Ironically, there's something less personal about generating personalized software with AI coding tools. Running open-source is like running the author's heart on your machine. You're exposed to their personality, opinions, and decisions whether right or wrong, down to the very last bit. In a world where we skip the human and cherry pick the ideas and features we like, we detract ourselves from all of this, turning the art of sharing into a process of extraction.
It feels somewhat lonely.
That said my remote Immich access works now so maybe none of this matters.
docker pull wujson/dovetail:latestAdd labels to containers you want to expose:
services:
webapp:
image: nginx:latest
labels:
dovetail.name: "webapp"
dovetail.port: "80"Run dovetail alongside your containers:
services:
dovetail:
image: wujson/dovetail:latest
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./dovetail-state:/var/lib/dovetail
restart: unless-stoppedYour service will be available at https://webapp.<tailnet-name>.ts.net.
Important: Dovetail must be able to reach your containers over the Docker network. This means they need to be on the same Docker network.
If Dovetail and your containers are defined in the same docker-compose.yml file, they automatically share a network. No additional configuration needed.
If you're running Dovetail separately from your containers, you need to create a shared network:
1. Create a shared network:
docker network create dovetail-network2. Add Dovetail to the network:
services:
dovetail:
image: wujson/dovetail:latest
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./dovetail-state:/var/lib/dovetail
networks:
- dovetail-network
restart: unless-stopped
networks:
dovetail-network:
external: true3. Add your containers to the same network:
services:
myapp:
image: nginx:latest
labels:
dovetail.name: "myapp"
dovetail.port: "80"
networks:
- dovetail-network
networks:
dovetail-network:
external: true| Variable | Description | Default |
|---|---|---|
TS_AUTHKEY |
Tailscale auth key (required, must be reusable) | - |
TS_STATE_DIR |
Directory for persisting Tailscale state | /var/lib/dovetail |
| Label | Required | Description |
|---|---|---|
dovetail.name |
Yes | Hostname for the service on your tailnet |
dovetail.port |
Yes | Container port to proxy |
Tailnet
ββββββββββββββββββββββββ¬βββββββββββββββββββββββ
βΌ βΌ βΌ
myapp1.ts.net myapp2.ts.net myapp3.ts.net
β β β
ββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββ
β Host β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Dovetail β β
β β β β
β β ββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ β β
β β β Docker β β Service Manager β β β
β β β Watcher βββββββΆβ βββββββββββ βββββββββββ βββββββββ β β β
β β ββββββββββββββββ β β myapp1 β β myapp2 β β ... β β β β
β β β β βββββββββββ€ βββββββββββ€ βββββββββ€ β β β
β β β β β tsnet β β tsnet β β β β β β
β β β β βββββββββββ€ βββββββββββ€ βββββββββ€ β β β
β β β β β proxy β β proxy β β β β β β
β β β β ββββββ¬βββββ ββββββ¬βββββ βββββ¬ββββ β β β
β β β βββββββββΌββββββββββββΌβββββββββββΌβββββββ β β
β βββββββββββΌβββββββββββββββββββββββΌββββββββββββΌβββββββββββΌββββββββββ β
β β β β β β
β βΌ βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Docker β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β container1 β β container2 β β container3 β β β
β β β :8080 β β :3000 β β :5432 β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
