{"id":13631436,"url":"https://github.com/subdavis/selfhosted","last_synced_at":"2026-01-31T10:36:50.201Z","repository":{"id":43075413,"uuid":"120554479","full_name":"subdavis/selfhosted","owner":"subdavis","description":"docker compose + traefik + tailscale","archived":false,"fork":false,"pushed_at":"2026-01-21T03:53:40.000Z","size":765,"stargazers_count":368,"open_issues_count":0,"forks_count":32,"subscribers_count":13,"default_branch":"main","last_synced_at":"2026-01-21T15:31:05.088Z","etag":null,"topics":["calibre","cloudflare","dnsmasq","docker","letsencrypt","seafile","traefik","ubuntu","wireguard"],"latest_commit_sha":null,"homepage":"https://subdavis.com/posts/2025-11-selfhosting/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/subdavis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-02-07T02:55:22.000Z","updated_at":"2026-01-21T03:53:45.000Z","dependencies_parsed_at":"2024-01-14T06:53:25.494Z","dependency_job_id":"67b2f82a-0dc4-46c3-b49b-a4ed8daf7f85","html_url":"https://github.com/subdavis/selfhosted","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/subdavis/selfhosted","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subdavis%2Fselfhosted","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subdavis%2Fselfhosted/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subdavis%2Fselfhosted/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subdavis%2Fselfhosted/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/subdavis","download_url":"https://codeload.github.com/subdavis/selfhosted/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subdavis%2Fselfhosted/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28938780,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T10:18:23.202Z","status":"ssl_error","status_checked_at":"2026-01-31T10:18:22.693Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["calibre","cloudflare","dnsmasq","docker","letsencrypt","seafile","traefik","ubuntu","wireguard"],"created_at":"2024-08-01T22:02:25.333Z","updated_at":"2026-01-31T10:36:50.189Z","avatar_url":"https://github.com/subdavis.png","language":"Python","readme":"# docker selfhosted services\n\n\u003e with docker-compose and traefik\n\n![Uptime Robot ratio (30 days)](https://img.shields.io/uptimerobot/ratio/m784171038-19b52e00f52a8d916ba46346)\n[![Build Status](https://drone.subdavis.com/api/badges/subdavis/selfhosted/status.svg)](https://drone.subdavis.com/subdavis/selfhosted)\n\nThis repo contains my production docker services accessible from anywhere over HTTPS using [traefik](https://traefik.io).  These services (and others) run on a single server.  **It used to be [rootless-mode](https://docs.docker.com/engine/security/rootless/)** but slirp4net was too slow and too much of the docker advanced configuration (permissions flags, mostly) were missing.\n\n* Jellyfin\n* Sonarr, Radarr, Prowlarr\n* Calibre Web\n* Kobo book downloader (kobodl)\n* Transmission torrent server\n* AdGuard Home DNS\n* Drone CI and runner\n* Duplicati\n* Watchtower\n* Cloudflare DNS Automation\n* Portainer\n\n# Documentation\n\nI've also written some intermediate to advanced generic usage docs for traefik, docker, pihole, and home networking.  These articles are generally applicable, but some may be more useful than others.\n\n* [Configuring Wildcard Certs for Traefik](docs/wildcard-certs.md)\n* [LAN-only Traefik Routing with ACME SSL](docs/lan-only-routes.md)\n* [Configuring PiHole with dnsmasq](docs/pihole-dnsmasq.md)\n* [EdgeRouter Backups over SSH (SCP)](docs/edgerouter-backups.md)\n* [Expand LVM to fill remaining disk](docs/ubuntu-expand-lvm.md)\n\nMore great documentation.\n\n* https://www.smarthomebeginner.com/traefik-2-docker-tutorial/\n* https://github.com/isaacrlevin/HomeNetworkSetup\n* https://github.com/htpcBeginner/docker-traefik\n\n## Prerequisites\n\n* A recent version of ubuntu server with `Docker CE` installed (see below)\n* A router or firewall capable of dnsmasq. I use a Ubiquiti EdgeRouter X.\n* A domain name.\n* A cloudflare account.\n\n### Home network prep\n\n* You need to make sure that ports 80 and 443 are port-forwarded through your router to whatever host this will be on.\n* Your server should be assigned a static private IP by DNS.  `ifconfig` will list your interfaces.\n* Refer to the [docker-pi-hole](https://github.com/pi-hole/docker-pi-hole) docs and [my docs](docs/pihole-dnsmasq.md) for further network setup related to that service.  Even though I use AdGuard Home, those docs are relevant.\n\n### DNS Configuration\n\n**UPDATE**: This is now done automatically with [Docker Traefik Cloudflare Companion](https://github.com/tiredofit/docker-traefik-cloudflare-companion).  Instructions below are left as an explanation of how this works.\n\nIn this setup, each container's service will serve from a different subdomain of your Cloudflare hosted zone dyndns subdomain.\n\n* Create an `A` record for `core.mydomain.com` to point to your public IP.\n* For each service, you'll need to create CNAME records for each `service.mydomain.com` to point to `core.mydomain.com` because all of your services are running on the same host but the host needs to be able to do virtual host routing based on domain name.\n* Your services will be publically available on `https://servicename.mydomain.com`.\n\n### Dynamic DNS (recommended)\n\nResolving the IP address of your home network is annoying because most DNS providers change your IP every now and again.  Services like No-IP combat this, but they aren't the most reliable.  However, setting DNS programatically is pretty easy with Cloudflare API.\n\n* Follow the instructions in [Configuring Wildcard Certs for Traefik](docs/wildcard-certs.md) to get this part set up.\n* You'll need to modify `.env` with your domain info, ACME email, and cloudflare API tokens.\n\n## Installation\n\n1. start with ubuntu lts\n1. [Enable Unattended Upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates)\n1. clone this repo\n1. Sign into any private docker registries\n1. install [docker](https://docs.docker.com/engine/install/)\n  a [Understanding UID remapping](https://medium.com/@tonistiigi/experimenting-with-rootless-docker-416c9ad8c0d6)\n  a. ignore the env exports it says to set, see below\n1. make sure `UsePAM yes` is set in `/etc/ssh/sshd_config` [read more](https://superuser.com/questions/1561076/systemctl-use-failed-to-connect-to-bus-no-such-file-or-directory-debian-9)\n\n```bash\ncd selfhosted\ncp .env.example .env # edit this\n\n# make mount points\nmkdir /media/local /media/primary /media/secondary\n\n# install mounts\nsystemctl link media-primary.mount\nsystemctl link media-secondary.mount\n\n# install logrotate\nsystenctl --user link $HOME/selfhosted/logrotate.timer\nsystenctl --user link $HOME/selfhosted/logrotate.service\nsystemctl --user enable logrotate.timer --now\n\n# enable traefik logrotate\ncp etc/traefik-logrotate.conf /etc/logrotate.d/traefik\n\n# Add to .profile\n# export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock\nnano .profile\n```\n\n[Set up docker daemon.json](https://forums.docker.com/t/rootless-docker-ip-range-conflicts/103341).  Otherwise, you may end up with subnet ranges inside your containers that overlap with the real LAN and make hosts unreachable.\n\n``` json\n{\n    \"default-address-pools\": [\n        {\"base\":\"172.16.0.0/16\",\"size\":24},\n        {\"base\":\"172.20.0.0/16\",\"size\":24}\n    ]\n}\n```\n\nEdit `/lib/systemd/system/user@.service` to include dependencies on mounts\n\n```conf\n[Unit]\nRequires=user-runtime-dir@%i.service media-primary.mount media-secondary.mount\n```\n\n## Automatic deployments and drone\n\n* Create a github api app. Follow drone setup instructions.\n* Make sure the user filtering config is set correctly so other users can't log in\n* Add secrets `ssh_key`, `ssh_host`, `ssh_user` for your deploy user.\n* Open `drone.yourdomain.com` and finish configuring your repo.\n\n## Adguard DNS\n\nYou may need to disable ubuntu's default dns service and remove resolf.conf [read more](https://www.smarthomebeginner.com/run-pihole-in-docker-on-ubuntu-with-reverse-proxy/).\n\nAfter disabling `systemd-resolved.service`, I ususally set a different DNS server in `/etc/resolv.conf` so that DNS doesn't break when I screw up the stack.\n\n`systemd-resolve --help` is your friend.\n\n[Also disable AdGuardHome's Rate Limiting](https://nikokultalahti.com/2025/04/09/fixing-server-misbehaving-error-in/) because miniflux will trigger it and throw `server misbehaving` errors.\n\n## WireGurad and subnet overlap\n\n* use `wg-quick` for simplicity\n* May need to [install or symlink resolvconf](https://superuser.com/questions/1500691/usr-bin-wg-quick-line-31-resolvconf-command-not-found-wireguard-debian)\n* Need to avoid [overlapping subnets](https://www.reddit.com/r/WireGuard/comments/bp01ci/connecting_to_services_through_vpn_when_the/).\n* Set MTU down to 1280 for issues with cellular networks, on BOTH sides of the connection.\n  * Update: As of September 19, had to drop to 1250 for TMobile LTE to work....\n\n* My subnet is `192.168.48.0/20`\n* The mask is `255.255.240.0`\n* The default LAN will be `192.168.52.0`\n* The gateway is `192.168.52.1`\n\n```\nGateway: 11000000.10101000.0011 | 0100.00000001\nMask:    11111111.11111111.1111 | 0000.00000000\n```\n\n* The upper 4 bits will be used for VLANs (16).\n* The lower 8 shoud belong to a single VLAN.\n\nUsing wireguard:\n\n```bash\nsudo systemctl enable wg-quick@peerN --now\n```\n\nI have aliases `wgup` and `wgdown` for this in my `.bashrc`.\n\n## IPv6\n\nSome references I encountered while rolling out ipv6.\n\n[My full edgerouter config](docs/config.boot)\n\n* [Docker IPV6](https://docs.docker.com/config/daemon/ipv6/)\n* [Kernel modules lazy-load ip6tables](https://github.com/moby/moby/issues/33605#issuecomment-307361421)\n  * `SYS_MODULE` capability doesn't seemt to do it. issuing an `ip6tables` dummy rule worked\n* [IPv6 Firewall Rules](https://community.ui.com/questions/Can-someone-let-us-know-the-added-default-IPv6-firewall-rule-mentioned-in-the-new-Edge-OS-2-01/9683f591-6cd2-4677-83c9-e90d2b7c3fbe)\n* Must [Block LAN to WLAN Multicast and Broadcast Data for ipv6 over wifi](https://community.ui.com/questions/IPv6-for-UniFi-WiFi/fa7109bb-c33f-4af4-9d98-dc82f0e31d99)\n* [You might have to disable some firewall stuff on the upstream ISP gateway](https://community.ui.com/questions/Allow-HTTPs-over-IPv6-in-firewall-Edgemax/c5f00707-4476-4b1b-91d4-7391f73aafa6)\n* [Disable ISP IPv6 DNS](https://kazoo.ga/dhcpv6-pd-for-native-ipv6/#)\n  * `no-dns` in `interface` config for `rdnss`\n* I have not been able to get wireguard to route ipv6 traffic under slirp4netns (rootlesss).  As a result, it is not possible to connect to wireguard via an ipv6 endpoint.  Wireguard for ios prefers ipv4, but the desktop client prefers ipv6.  Recommend creating an ipv4-only DNS record such as `wireguard4.domain.com` to force ipv4.\n\n## Other useful nonsense\n\n```bash\n# set own IP, delete set\nifconfig eth0 192.168.1.5 netmask 255.255.255.0 up\nifconfig en1 delete 192.168.1.5\n```\n\n\n","funding_links":[],"categories":["Others","HarmonyOS"],"sub_categories":["Windows Manager"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubdavis%2Fselfhosted","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsubdavis%2Fselfhosted","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubdavis%2Fselfhosted/lists"}