├── README.md ├── caddy ├── Caddyfile ├── caddy.service └── install-caddy-with-cloudflare.sh └── containers ├── compose.yaml └── immich ├── .env └── compose.yaml /README.md: -------------------------------------------------------------------------------- 1 | # tailscale-dev/video-caddy-custom-domains 2 | 3 | This repo contains the supporting materials for a video on the Tailscale YouTube channel about using Caddy with custom domains in conjunction with Tailscale to provide secure remote access to self-hosted services. 4 | 5 | [![Remotely access and share your self-hosted services](https://img.youtube.com/vi/Vt4PDUXB_fg/maxresdefault.jpg)](https://youtu.be/Vt4PDUXB_fg) 6 | 7 | We're going to use Tailscale and the reverse proxy Caddy to share self-hosted services on your Tailnet with friends family. 8 | 9 | In today's video we focus on Immich - a self-hosted photo backup tool, Audiobookshelf - an audiobook server, and Jellyfin - an open source media server. 10 | 11 | Choose your own adventure in today's video and use it as a resource to share with friends or relatives who you'd like to be access your services. If you're looking for the chapter to send your remote users to, it's at 10:44 "instructions for remote users". 12 | 13 | ## Links 14 | 15 | + [Cloudflare - Generating a Cloudflare API token documentation](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) 16 | + [Namecheap - How to register a domain name](https://www.namecheap.com/support/knowledgebase/article.aspx/10072/35/how-to-register-a-domain-name/) 17 | + [Caddy - Reverse proxy quick start](https://caddyserver.com/docs/quick-starts/reverse-proxy) 18 | + [Immich - Self-hosted backup solution for photos and videos on mobile devices](https://immich.app/) 19 | + [Tailscale - Running Tailscale in LXC containers](https://tailscale.com/kb/1130/lxc-unprivileged) 20 | -------------------------------------------------------------------------------- /caddy/Caddyfile: -------------------------------------------------------------------------------- 1 | (cloudflare) { 2 | tls { 3 | dns cloudflare notArealToken-generateme 4 | } 5 | } 6 | 7 | # immich 8 | immich.rdu.dotsandstuff.dev { 9 | reverse_proxy http://10.42.0.42:2283 10 | import cloudflare 11 | } 12 | 13 | # audiobookshelf 14 | audiobooks.rdu.dotsandstuff.dev { 15 | reverse_proxy http://10.42.0.42:2284 16 | import cloudflare 17 | } 18 | 19 | # jellyfin 20 | jellyfin.rdu.dotsandstuff.dev { 21 | reverse_proxy http://10.42.0.42:2285 22 | import cloudflare 23 | } 24 | 25 | # proxmox 26 | #px.rdu.dotsandstuff.dev{ 27 | # reverse_proxy https://100.88.250.125:8006 { 28 | # transport http { 29 | # tls_insecure_skip_verify 30 | # } 31 | # } 32 | # import cloudflare 33 | #} 34 | -------------------------------------------------------------------------------- /caddy/caddy.service: -------------------------------------------------------------------------------- 1 | # caddy.service 2 | # 3 | # For using Caddy with a config file. 4 | # 5 | # Make sure the ExecStart and ExecReload commands are correct 6 | # for your installation. 7 | # 8 | # Place this file at /etc/systemd/system/caddy.service 9 | # systemctl daemon-reload 10 | # 11 | # See https://caddyserver.com/docs/install for instructions. 12 | # 13 | # WARNING: This service does not use the --resume flag, so if you 14 | # use the API to make changes, they will be overwritten by the 15 | # Caddyfile next time the service is restarted. If you intend to 16 | # use Caddy's API to configure it, add the --resume flag to the 17 | # `caddy run` command or use the caddy-api.service file instead. 18 | 19 | [Unit] 20 | Description=Caddy 21 | Documentation=https://caddyserver.com/docs/ 22 | After=network.target network-online.target 23 | Requires=network-online.target 24 | 25 | [Service] 26 | Type=notify 27 | User=caddy 28 | Group=caddy 29 | ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile 30 | ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force 31 | TimeoutStopSec=5s 32 | LimitNOFILE=1048576 33 | LimitNPROC=512 34 | PrivateDevices=yes 35 | PrivateTmp=true 36 | ProtectSystem=full 37 | AmbientCapabilities=CAP_NET_BIND_SERVICE 38 | 39 | [Install] 40 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /caddy/install-caddy-with-cloudflare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # downloads the caddy binary with the cloudflare plugin enabled 3 | # must be run as root 4 | 5 | caddy_path="/usr/local/bin/caddy" 6 | 7 | if [ "$EUID" -ne 0 ] 8 | then echo "Please run as root" 9 | exit 10 | fi 11 | 12 | if [ -f "$caddy_path" ]; then 13 | echo "$caddy_path exists..." 14 | echo "Nothing to do. Exiting." 15 | exit 1 16 | else 17 | curl "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fcaddy-dns%2Fcloudflare" -o $caddy_path 18 | chmod +x $caddy_path 19 | fi 20 | 21 | cp caddy.service /etc/systemd/system/caddy.service 22 | systemctl daemon-reload 23 | mkdir -p /etc/caddy 24 | useradd caddy 25 | mkdir /home/caddy 26 | chown -R caddy:users /home/caddy 27 | 28 | echo "You'll want to modify you Caddyfile next..." 29 | echo "Then copy it to `/etc/caddy/Caddyfile`" 30 | -------------------------------------------------------------------------------- /containers/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "3.8" 3 | services: 4 | jellyfin: 5 | image: jellyfin/jellyfin 6 | container_name: jellyfin 7 | volumes: 8 | - /mnt/data/appdata/jellyfin:/config 9 | - /mnt/data/media:/data:ro 10 | - /dev/shm:/transcode 11 | ports: 12 | - 2285:8096 13 | environment: 14 | - PUID=1000 15 | - PGID=1000 16 | - TZ=America/New_York 17 | - JELLYFIN_PublishedServerUrl=jellyfin.rdu.dotsandstuff.dev 18 | hostname: jf-rdu 19 | restart: unless-stopped 20 | abs: 21 | image: advplyr/audiobookshelf 22 | container_name: abs 23 | volumes: 24 | - /mnt/data/media/audiobooks/library:/audiobooks:ro 25 | - /mnt/data/media/audiobooks/podcasts:/podcasts 26 | - /mnt/data/appdata/audiobookshelf/metadata:/metadata 27 | - /mnt/data/appdata/audiobookshelf/config:/config 28 | ports: 29 | - 2284:80 30 | restart: unless-stopped 31 | -------------------------------------------------------------------------------- /containers/immich/.env: -------------------------------------------------------------------------------- 1 | # You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables 2 | 3 | # The location where your uploaded files are stored 4 | UPLOAD_LOCATION=/mnt/data/photos/uploads 5 | 6 | # The Immich version to use. You can pin this to a specific version like "v1.71.0" 7 | IMMICH_VERSION=release 8 | 9 | # Connection secrets for postgres and typesense. You should change these to random passwords 10 | TYPESENSE_API_KEY=some-random-text 11 | DB_PASSWORD=postgres 12 | 13 | # IMMICH_WEB_URL=http://immich-web:3000 14 | # IMMICH_SERVER_URL=http://immich-server:3001 15 | 16 | # The values below this line do not need to be changed 17 | ################################################################################### 18 | DB_HOSTNAME=immich_postgres 19 | DB_USERNAME=postgres 20 | DB_DATABASE_NAME=immich 21 | 22 | REDIS_HOSTNAME=immich_redis -------------------------------------------------------------------------------- /containers/immich/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "3.8" 3 | services: 4 | immich-server: 5 | container_name: immich_server 6 | image: ghcr.io/immich-app/immich-server:release 7 | command: ["start.sh", "immich"] 8 | volumes: 9 | - ${UPLOAD_LOCATION}:/usr/src/app/upload 10 | - /mnt/data/photos/images:/photos/images:ro #full 11 | - /etc/localtime:/etc/localtime:ro 12 | env_file: 13 | - .env 14 | ports: 15 | - 2283:3001 16 | networks: 17 | - immich 18 | depends_on: 19 | - redis 20 | - database 21 | restart: unless-stopped 22 | immich-microservices: 23 | container_name: immich_microservices 24 | image: ghcr.io/immich-app/immich-server:release 25 | command: ["start.sh", "microservices"] 26 | volumes: 27 | - ${UPLOAD_LOCATION}:/usr/src/app/upload 28 | - /mnt/data/photos/images:/photos/images:ro #full 29 | - /etc/localtime:/etc/localtime:ro 30 | networks: 31 | - immich 32 | env_file: 33 | - .env 34 | depends_on: 35 | - redis 36 | - database 37 | restart: unless-stopped 38 | immich-machine-learning: 39 | container_name: immich_machine_learning 40 | image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} 41 | volumes: 42 | - /mnt/data/appdata/immich/model-cache:/cache 43 | env_file: 44 | - .env 45 | networks: 46 | - immich 47 | restart: unless-stopped 48 | redis: 49 | container_name: immich_redis 50 | image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3 51 | networks: 52 | - immich 53 | restart: unless-stopped 54 | database: 55 | container_name: immich_postgres 56 | image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 57 | env_file: 58 | - .env 59 | environment: 60 | POSTGRES_PASSWORD: ${DB_PASSWORD} 61 | POSTGRES_USER: ${DB_USERNAME} 62 | POSTGRES_DB: ${DB_DATABASE_NAME} 63 | PG_DATA: /var/lib/postgresql/data 64 | networks: 65 | - immich 66 | volumes: 67 | - /mnt/data/appdata/immich/db:/var/lib/postgresql/data 68 | restart: unless-stopped 69 | 70 | networks: 71 | alex_default: 72 | name: alex_default 73 | external: true 74 | immich: --------------------------------------------------------------------------------