You want your own Git host. Maybe you're getting off GitHub, maybe you just want somewhere private to keep the repos that shouldn't be on someone else's servers. So you go looking, and every guide hands you the same shape: a public DNS record, ports 80 and 443 open to the entire internet, a reverse proxy, a certificate, and a login page that anyone on earth can now knock on.
For a private code host, that's a strange trade. Nothing about "my repos, for me and three collaborators" requires a public address. If the only people who should reach it are people you already trust, put it on your tailnet and the whole category of internet facing problems stops existing. No open ports, and no login page getting scanned at three in the morning.
The Compose file for this is short. What makes it worth writing down is the sidecar pattern it uses, which is not obvious the first time, and a handful of settings that decide whether it works at all. I built this from nothing on a fresh Ubuntu 26.04 box to check it, and the settings that went wrong were not the ones I expected, so those get their own section at the end.
TL;DR. Run Tailscale as its own container and give Forgejo network_mode: service:ts-forgejo so Forgejo has no published ports and its own tailnet identity. Point TS_SERVE_CONFIG at a serve JSON file and you get real HTTPS on forgejo.your-tailnet.ts.net with no port 80 and no HTTP-01 challenge. Persist /var/lib/tailscale or every restart creates a brand new node. Tag the device so its node key never expires. Do not set START_SSH_SERVER, because the image already runs sshd on port 22.






