diff options
| author | Karan Jayachandra <mail@karanjayachandra.com> | 2026-07-19 20:55:14 +0200 |
|---|---|---|
| committer | Karan Jayachandra <mail@karanjayachandra.com> | 2026-07-19 20:55:14 +0200 |
| commit | 52224b0890203eb96911c6944fedb4c2ceb2935f (patch) | |
| tree | 736da0954d0cee809fd41b38d8d579c82dc9bccf | |
Docker compose
| -rw-r--r-- | .claude/settings.local.json | 7 | ||||
| -rw-r--r-- | .env.enc | 31 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .sops.yaml | 3 | ||||
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | caddy/Caddyfile | 40 | ||||
| -rw-r--r-- | caddy/Dockerfile | 8 | ||||
| -rw-r--r-- | docker-compose.yml | 143 |
8 files changed, 245 insertions, 0 deletions
diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..15b1e4b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(docker compose *)" + ] + } +} diff --git a/.env.enc b/.env.enc new file mode 100644 index 0000000..a73ee65 --- /dev/null +++ b/.env.enc @@ -0,0 +1,31 @@ +#ENC[AES256_GCM,data:9AZe+JCrAJgApE+oQlBIacT8Zs7Qu2WQ,iv:3VEh1f3XXQtf79jdmItPcKt/skt7U8HpMZhWO4Y8Blg=,tag:69XcDdKug3qpK1nHzaS8cQ==,type:comment] +PUID=ENC[AES256_GCM,data:prO7Hw==,iv:XqNA9vtb+ebZbU/6jpDvsOnuMEs1vf3T5e0DcgzBDAU=,tag:TtTzb1ladTDtE5nzytbzkQ==,type:str] +PGID=ENC[AES256_GCM,data:xVM=,iv:LiQG+qpjyyJMS0Y/foBpdZ2dPeVDlDx1Ziy6Zolq8Vc=,tag:7RhZTWE+4YPeyogwus7mkg==,type:str] +TZ=ENC[AES256_GCM,data:5BolCAxAFUYd5RagmjeE5Q==,iv:5hofxcioR0kG0rA+PIXFKdqs0r/lTaycDq/kL1DqHhs=,tag:06P8+pF4XqfsN5SaduEDKw==,type:str] +#ENC[AES256_GCM,data:heKOmgUqCwdkGT191OGv/LXuc5Hf,iv:p69H+q/FTGbYYoK5nQC1KRofOJP8LQA1Ppq+nhlu2pw=,tag:oKNmxsp/y9Y3alhzdXSCvA==,type:comment] +#ENC[AES256_GCM,data:1khiCdJvFziwoQyyO39sW4GQFTMHWSOH2f1EeXs0uMVNG+iw,iv:45Q7dGfpK8j8FEU+EvZURBpHBCYx6N0jP7R4gLpYVqA=,tag:j/vSTb4AA2MIKsVJoMmhnQ==,type:comment] +TS_IP=ENC[AES256_GCM,data:F/GmBGZkcR2INUKUbc0w,iv:zFjweg6n6iwGSopp9bX2LrV6ZPIpCKaMNSW8pKOnILY=,tag:hwZjPVdCpDfMJcY2O7ncjQ==,type:str] +DOMAIN=ENC[AES256_GCM,data:ktPl91SzSbYRyA==,iv:ikOGrPitikBLxISgh+s3RDIaNUkEC5FFTBfZ4LIG3Zk=,tag:/WkRSgOLY0oBbmNaTa5AJg==,type:str] +#ENC[AES256_GCM,data:kxB6douZV+eb1roeg5GqLE9nitG+jfI=,iv:ti6sniUI1WyntrMhucbq9OP6Iy1fcnh/zQegwMPU4I8=,tag:1TMYsRX5ri2lBW/PcPNmiQ==,type:comment] +#ENC[AES256_GCM,data:MGoxzGN8OekHRRh7m8sBmLsewc4rRyzrcESKct2gU91JP/+5/7Tt206jce12CBTkJmAUoxfRJpkyzI9mye+uerk6mZfu1h0=,iv:lyeoQgYY6QvGfO5P6KL2S8oKYtLxZlACsrVBdA2eG6E=,tag:8J9MvJvcowkzHM4zQ9A1TQ==,type:comment] +#ENC[AES256_GCM,data:XUfVYYSqnQGQTvqU+uu6oBMPcURbd0PQBC8synZRS8pPIeXdneviZAMFxoCiJzc06gzgEsWi3MRK,iv:RIA1bNPIDhrZp0wJTUyQQog96fV6a5qSumTRg54WQgc=,tag:xb+Ssg7MTvn1v6ILVf10Rg==,type:comment] +GANDI_BEARER_TOKEN=ENC[AES256_GCM,data:uacfd7uOcOehUZuMqno8YoITogu0pXGubCy8B+R3Uvy1ohqF/cjG0Q==,iv:ZDV2D1COWekXzzK+kkFLYjiMUvDR2Yrrb9RTRgSMmaE=,tag:JmIyFvD4EsW2WDAPrvg7lA==,type:str] +#ENC[AES256_GCM,data:aRd0GgWFF+u6dEcCJO7Unjh1w7zbFt/4I2Uyd6JfFtUov3ou/GHNIeBuGMtDhw==,iv:5gY9yVtXiTYxvQpqVLEIkhsyVqbxHtXX818+R0+T/r4=,tag:eRkai1Nl3JzR0rl4YpJ/mA==,type:comment] +MOVIES_PATH=ENC[AES256_GCM,data:Uk0kKuYDgVsRaPIlFKU3,iv:Ozq7frOcW2K3xl3QxDBOSpNDnpWdpSAfkH//kvv0g3k=,tag:JLeBTOL9kzNKjIEs5CmkkQ==,type:str] +TV_PATH=ENC[AES256_GCM,data:6ODQmkjvgYQ8rwj/PaN7oho=,iv:ZdHb76tms5L75Lzku/oKVGQWdYUw98hGKzUbbNUGM9M=,tag:wHbQEsY9BYol0VEaMxE84Q==,type:str] +DOWNLOADS_PATH=ENC[AES256_GCM,data:RSW8rtnra1rSeACy38InFaMV,iv:8BNz3LT5QzFi/Fjuj6olXDYbvOCF8Qe+i8BoPPw6WP0=,tag:u3fDDeasEKstaDkOkOzfBg==,type:str] +#ENC[AES256_GCM,data:HRM0VGIYHFOTAcNt7G8hmVHMlBmIpS9PDgtsnciN,iv:T0D0dgiOZdfLJkdpPuzn2xboN390AvDe9m0fjEF82gY=,tag:WCxQ13sYfPmug6stmcLqPQ==,type:comment] +VPN_SERVICE_PROVIDER=ENC[AES256_GCM,data:1LF8+ABS3g==,iv:h8pTJ9xWNFWPdyoolLL5Xb92wOMvPs+8Q9lKzi5gzeg=,tag:3xIDm/ishURdOTnGRSnc9A==,type:str] +VPN_TYPE=ENC[AES256_GCM,data:BqFIBDTkIBsI,iv:l6wJL0mpTjw48scMLzOQeO9cp0jqYRACpGCcPuGXLqE=,tag:6omGgVBp6Myp6mJiowv9lA==,type:str] +#ENC[AES256_GCM,data:4JuH3JrcTRlG/bG2iKpjedIYmvfBSDrdQofFwAv3OZl1wHvVX2mD9hgx/jEJn86RrSyYimEw6ADs2XCW2uHQxlY=,iv:MS6ysSwuaTm+6XeaFHTG26hUfyZ9I/dMdnq3NAxnkuo=,tag:UlMwM4LSubZwzzDFpf9xRg==,type:comment] +WIREGUARD_PRIVATE_KEY=ENC[AES256_GCM,data:pAf2FED7DbNUm4XkXSeZg9Z7nYO78tjlfCSIxLaNS/6YVCwFotPrah4Y8Po=,iv:PsUNp1jaIXnGPJO+B0qtYxjTh34TJ3h+BnihH5DRRaM=,tag:/n/7Mp0RbXgERpMrd0o1NA==,type:str] +#ENC[AES256_GCM,data:r4xdkP2UmmlMI+hhtLJErb+aChgZfHHFK9Z60ABtCXGCPjmXLG3ovd33Qj7xmUXbpZ6uf6n3dR2LNcE=,iv:xVuVJuplA5YDjKfKxQXAN1Cirr/sYp63Sry+EY3PLwE=,tag:ZzhP2QYtyhqDJkTqO2C6kA==,type:comment] +WIREGUARD_ADDRESSES=ENC[AES256_GCM,data:gTBapWrxKpRdqo+KW7A=,iv:rvuHbtQSAkfIpoO2ReP4QQYWN0czvnAzVdxbkK2Up0M=,tag:GuLAiOL5f928FDPeTXtq3Q==,type:str] +#ENC[AES256_GCM,data:rLczdt3MHicSSNrTF0OsrD3vWaB7i9Z+KJ8fpQ7V,iv:KG6jD3UMp5ExzMqftpu3JJf9+pQVYL3P+V4+gLa2hLA=,tag:Quw6Bwp9TGUbiFF5SazWpQ==,type:comment] +SERVER_CITIES=ENC[AES256_GCM,data:rkQUnIyNpAFH,iv:hUX4zjtufG5PsXEb793CbLzoPQdCGScQvxvOVwCEd1Q=,tag:opoc99h2s/wj90iPzX8+Nw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRNFZ1WWJMZ0ZZbFdvZkp1\ndlp6SkQ4c3IyMjJtenZWNHZteFowRkJ3SW1rCmkvbmVuMGJPZU9ZMk50Q0pYdzdB\neDhnVU90TW5RbWdEVXR1L0hvczZiVU0KLS0tIEhaUjJ6Ty9wZWNDKzMxVnh6Y2J3\ndktwekt6NnRZdEJTbFhEOHNaTW5MM2sK1uE4qdx9jRwnupHLtTyNgGf0JZ6NveJ+\n44Q2fzqO7upn2FIqhYfbj1KG3Nku7gSH5xDb16X7gyMxfqy5bDR/cQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1sv5yc279d8j66fx36awxhdnsqjp3pv8x9yc0ak6xa9qy9egg6e8qegccg4 +sops_lastmodified=2026-07-19T18:54:05Z +sops_mac=ENC[AES256_GCM,data:h/X6+jefhCqe+Unc0CWVEmi27E55eleu+PY9T+vP7zIkpkybGK3el3foRBuJA15tDKaj7bZSwKo1a+mpMxi+bWsvrTEjNY3kuMeefLJzIfKSmfP8NVsn4cYJ5Iy8Rt+cfKXfD9JMu33rJ2rvnGD6I3p1JQAikOfzPeNo8urK/5E=,iv:5D3MmNTrqLaNzaQEw6mSNR51wNfdFHiXGK03XVImXI8=,tag:3S1tHW/VveAWdAkefztrMA==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.13.2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..bb2fede --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,3 @@ +creation_rules: + - path_regex: \.env$ + age: age1sv5yc279d8j66fx36awxhdnsqjp3pv8x9yc0ak6xa9qy9egg6e8qegccg4 diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc24110 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# goodall + +Docker Compose media stack (Jellyfin, Radarr, Sonarr, Prowlarr, Bazarr, qBittorrent) fronted by Caddy, reachable only over Tailscale. + +Deployed on the NAS at `/volume2/docker/goodall` (host `karan@100.121.200.111`, tailnet name `goodall.taildc6b0a.ts.net`). This local directory is the source of truth for edits; redeploy a changed file with: + +``` +ssh karan@100.121.200.111 "cat > /volume2/docker/goodall/<file>" < ./<file> +ssh karan@100.121.200.111 "cd /volume2/docker/goodall && sudo docker compose up -d" +``` + +See [MIGRATION_STATUS.md](MIGRATION_STATUS.md) for current deployment status and remaining work. diff --git a/caddy/Caddyfile b/caddy/Caddyfile new file mode 100644 index 0000000..9d941d0 --- /dev/null +++ b/caddy/Caddyfile @@ -0,0 +1,40 @@ +{ + email me@karanj.com +} + +(tls_gandi) { + tls { + dns gandi {env.GANDI_BEARER_TOKEN} + propagation_timeout -1 + } +} + +jellyfin.{$DOMAIN} { + import tls_gandi + reverse_proxy jellyfin:8096 +} + +movies.{$DOMAIN} { + import tls_gandi + reverse_proxy radarr:7878 +} + +shows.{$DOMAIN} { + import tls_gandi + reverse_proxy sonarr:8989 +} + +index.{$DOMAIN} { + import tls_gandi + reverse_proxy prowlarr:9696 +} + +subs.{$DOMAIN} { + import tls_gandi + reverse_proxy bazarr:6767 +} + +dl.{$DOMAIN} { + import tls_gandi + reverse_proxy gluetun:8123 +} diff --git a/caddy/Dockerfile b/caddy/Dockerfile new file mode 100644 index 0000000..f1b6d2c --- /dev/null +++ b/caddy/Dockerfile @@ -0,0 +1,8 @@ +FROM caddy:2-builder AS builder + +RUN xcaddy build \ + --with github.com/caddy-dns/gandi + +FROM caddy:2 + +COPY --from=builder /usr/bin/caddy /usr/bin/caddy diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f93b205 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,143 @@ +services: + gluetun: + image: qmcgaw/gluetun:v3.40.0 + container_name: gluetun + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + environment: + - VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER} + - VPN_TYPE=${VPN_TYPE} + - WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY} + - WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES} + - SERVER_CITIES=${SERVER_CITIES} + - FIREWALL_INPUT_PORTS=8123 + - TZ=${TZ} + volumes: + - ./config/gluetun:/gluetun + networks: + - media + restart: unless-stopped + + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + network_mode: "service:gluetun" + depends_on: + - gluetun + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + - WEBUI_PORT=8123 + volumes: + - /volume2/docker/linuxserver_qbittorrent-1/config:/config + - ${DOWNLOADS_PATH}:/downloads + restart: unless-stopped + + jellyfin: + image: lscr.io/linuxserver/jellyfin:latest + container_name: jellyfin + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - /volume2/docker/jellyfin/config:/config + - ${MOVIES_PATH}:/movies + - ${TV_PATH}:/shows + devices: + - /dev/dri:/dev/dri + networks: + - media + restart: unless-stopped + + radarr: + image: lscr.io/linuxserver/radarr:latest + container_name: radarr + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - /volume2/docker/radarr:/config + - ${MOVIES_PATH}:/movies + - ${DOWNLOADS_PATH}:/downloads + networks: + - media + restart: unless-stopped + + sonarr: + image: lscr.io/linuxserver/sonarr:latest + container_name: sonarr + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - /volume2/docker/sonarr:/config + - ${TV_PATH}:/tv + - ${DOWNLOADS_PATH}:/downloads + networks: + - media + restart: unless-stopped + + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + container_name: prowlarr + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - /volume2/docker/prowlarr:/config + networks: + - media + restart: unless-stopped + + bazarr: + image: lscr.io/linuxserver/bazarr:latest + container_name: bazarr + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - /volume2/docker/bazarr:/config + - ${MOVIES_PATH}:/movies + - ${TV_PATH}:/tv + networks: + - media + restart: unless-stopped + + caddy: + build: ./caddy + container_name: caddy + ports: + - "${TS_IP}:443:443" + environment: + - GANDI_BEARER_TOKEN=${GANDI_BEARER_TOKEN} + - DOMAIN=${DOMAIN} + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + networks: + - media + depends_on: + - jellyfin + - radarr + - sonarr + - prowlarr + - bazarr + - gluetun + restart: unless-stopped + +networks: + media: + driver: bridge + +volumes: + caddy_data: + caddy_config: |
