aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.enc8
-rw-r--r--README.md6
-rw-r--r--caddy/Caddyfile7
-rw-r--r--docker-compose.yml80
4 files changed, 96 insertions, 5 deletions
diff --git a/.env.enc b/.env.enc
index 627ea75..37bcb1c 100644
--- a/.env.enc
+++ b/.env.enc
@@ -5,7 +5,7 @@ TZ=ENC[AES256_GCM,data:fVGxWW9EwgEEsvoINN91tw==,iv:07SQemVbCe7lBDP93ksoYTgiipZsu
#ENC[AES256_GCM,data:DHskclKhT/ruYtSSw3sd5ype62a/,iv:qxyrn5tSrXqvvdel5NRJZYFPVnYLznYnTEzRiJ7WEXw=,tag:Ip53lQRy6pcUa3rPyCbIeQ==,type:comment]
#ENC[AES256_GCM,data:gwa8g32Juth7AbOkgFbMbDS5RiZlyv127ctgI5IytLN5egIa,iv:UacZcnKuF0zQfquqKsBd8tckKP7t3jbL4MIEYRIesJI=,tag:3Yw6LmEii5V3EqTy/RgQHQ==,type:comment]
TS_IP=ENC[AES256_GCM,data:7yTEnV0qUlot4hFOtyCL,iv:B9D3copRDvuecEb+TV5V6c77JDhlSZ+Fhlw5ZGgkQbs=,tag:zN8lEQ31/Zxzu9IGss4i4g==,type:str]
-DOMAIN=ENC[AES256_GCM,data:EmH4SUfcGewX4g==,iv:JZukqc4yI3Gf7Tb/AukB1oFjXJ0CzPLzNplCOEGxAT4=,tag:8nACxPumMsQi8QJxIlOQgA==,type:str]
+DOMAIN=ENC[AES256_GCM,data:QdxXEsBlPWsUfO0fMjc=,iv:87CFbrDCXuZNumWq/udGVU9seiHVKLDnW2A9vuWVRkE=,tag:KiS9Of3W3SjGpKoobDIGlA==,type:str]
#ENC[AES256_GCM,data:uyTjemwDVhTCMvM7ta3mIQIzFHfSj6I=,iv:mkngrcKbRbZvfF2lHq1nLGdhF5o0VIamDqbvTq1BgOY=,tag:fVibo8oQu3YGDWJOsOEEZg==,type:comment]
#ENC[AES256_GCM,data:mgmFC0i6d5yHMYAJkKp/DqnYZlaS1Ps+f3WUpb4xUkquaIxeK85N+aROMRL6Gw1vJlFSckq24jsLO5rDpu4+KJER66qYv/s=,iv:OtJf7PFUrkYL+WfK8pYaaLT/OoarWMYuS27tw45Epqw=,tag:I3gobI8p24IVAw6WWHGQ2w==,type:comment]
#ENC[AES256_GCM,data:WrhB5Hm7iJkYEab72zzcCIIpbIs/9byPMc+QyPUWPPx7085deubT8WfOt5nbexIi9VuMssm30ULX,iv:hFY7ONM0F/B2t9pBpco8P7IgrC2lcve5/Nl6+856FKA=,tag:bwP0u15xmOpZb9i9D5oO7A==,type:comment]
@@ -31,7 +31,7 @@ BAZARR_API_KEY=ENC[AES256_GCM,data:gr1guUQ17CUxuwPzDGtXegFPVA4CgIdJvWXAdxXf8nE=,
LAN_IP=ENC[AES256_GCM,data:sbYMEnIuCIcTU5Ek,iv:d5NysqFShXysQR5PUlUESiY3Uw0ri6MKgK4wi3oAhX8=,tag:Z/6DNZevYFWE4cU7wP3pmw==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2WFE5QzNXNlhxakx3T3Yv\nV1FqVXBsQ21DdDB4R05hUGpqM09Qd0FsTno0CjJlL1lYNDNHVDBsRnRJNkdNcnJ0\nZnVVRFFWazcxUmV0emNTMzVLajNMdDQKLS0tIHBBWGRuWXdIWHYrcjJTeXVzLzZE\nVnpTeUNrQ205Tm1WODRLQkdablFaajAKM7W7VXTND6e33myo4rP/kFWQ938gFX1Y\nTRXBBCFMxJwRAjp9XE2IFsxxDHaHJnj/6O3k6kEyQsGZUwbWKfz7Pg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1sv5yc279d8j66fx36awxhdnsqjp3pv8x9yc0ak6xa9qy9egg6e8qegccg4
-sops_lastmodified=2026-07-26T16:56:26Z
-sops_mac=ENC[AES256_GCM,data:1OcJFIhZmpD37vzwyC6H9LaPFmHeSy4ky6L9mn2W+bxeS+efR30VYvdjjqd/ejoExTgoEtt+Iq2nDoLdC+bv9ZUcBcTsQu6IDcYQ1lc/KyFN1CYkWIYPgOhT2ePnMfKQcayedx1zmVaQlumdAeM1XNadDul2hTneontZtyUyCzs=,iv:/cJGM+Qmx9GK+VinC6gBq8O9EP5+UL87uVwoE4O0zrI=,tag:6gfNlqYTT5jpXPajvKeUaA==,type:str]
+sops_lastmodified=2026-08-30T20:35:49Z
+sops_mac=ENC[AES256_GCM,data:92Jy7rI/K0C0z6izo0vSAQYFO8dvHwGLdJ0uLCLkopMxu7wY0mtQBGuVEH+hSQiRnjzqDvC3qjK1aMiArysMmYd5bCargCAquYD+AsmHqH+0efT7qY4S+yvS+dZNFYb4oOIzXMq6u4fOSRaW4r/yMZUxwoRspyaM53cb0mYkS+8=,iv:FLsdwNUooqN6jOR32pzwjxQlgN/DTeQFSaS/9LqCAqw=,tag:apzKXT1jQVNjH1zx+PEt5g==,type:str]
sops_unencrypted_suffix=_unencrypted
-sops_version=3.13.2
+sops_version=3.13.3
diff --git a/README.md b/README.md
index c0c887a..cba01b8 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,12 @@ All secrets and per-deployment config (VPN credentials, Gandi DNS token, LAN/Tai
All images (including Gluetun) track `:latest`. Nothing auto-updates — re-run `./deploy.sh` to pull and recreate everything. This is deliberate: `gluetun` and `qbittorrent` share a network namespace (`network_mode: "service:gluetun"`), so updates need to recreate both together, which a project-level `docker compose pull && up -d` does correctly and a per-container auto-updater would not.
+## Monitoring
+
+Every service has a real `HEALTHCHECK` (not just "is the process alive" — e.g. qbittorrent's also curls an external IP through gluetun's tunnel, since its WebUI keeps responding even when the VPN is dead). `autoheal` watches Docker's health status and runs `docker restart` on anything reporting `unhealthy`. `uptime-kuma` (at `status.karanj.com`) is the dashboard — monitors aren't config-as-code, so after a fresh deploy log in and add a "Docker Container" monitor per service plus an HTTP monitor per public hostname.
+
+**Caveat:** `qbittorrent` shares `gluetun`'s network namespace (`network_mode: "service:gluetun"`), and autoheal restarts containers individually — it has no notion of "restart this one's dependents too." Both carry equivalent connectivity healthchecks so they should flip `unhealthy` and get restarted within a cycle or two of each other, but it's not perfectly atomic. If qbittorrent ever looks stuck after a gluetun restart, `docker compose restart gluetun qbittorrent` (together) is the manual fallback — that's what actually fixed the Aug 2026 VPN outage.
+
## Gotchas
- **Gluetun blocks inbound container-to-container traffic by default** — `FIREWALL_INPUT_PORTS=8123` in `docker-compose.yml` is required, or qBittorrent's WebUI (proxied by Caddy for `dl.karanj.com`) returns a 502.
diff --git a/caddy/Caddyfile b/caddy/Caddyfile
index 4f38033..4fe36c5 100644
--- a/caddy/Caddyfile
+++ b/caddy/Caddyfile
@@ -39,7 +39,12 @@ dl.{$DOMAIN} {
reverse_proxy gluetun:8123
}
-nas.{$DOMAIN} {
+status.{$DOMAIN} {
+ import tls_gandi
+ reverse_proxy uptime-kuma:3001
+}
+
+nas.karanj.com {
import tls_gandi
reverse_proxy https://host.docker.internal:9443 {
transport http {
diff --git a/docker-compose.yml b/docker-compose.yml
index 6277bdd..8fd2d8d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,6 +18,8 @@ services:
- ./config/gluetun:/gluetun
networks:
- media
+ labels:
+ - "autoheal=true"
restart: unless-stopped
qbittorrent:
@@ -34,6 +36,14 @@ services:
volumes:
- /volume2/docker/linuxserver_qbittorrent-1/config:/config
- ${DOWNLOADS_PATH}:/downloads
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS --max-time 5 http://localhost:8123/ >/dev/null && curl -fsS --max-time 5 https://1.1.1.1 >/dev/null"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
restart: unless-stopped
jellyfin:
@@ -53,6 +63,14 @@ services:
- "${LAN_IP}:8096:8096"
networks:
- media
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS http://localhost:8096/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
restart: unless-stopped
radarr:
@@ -68,6 +86,14 @@ services:
- ${DOWNLOADS_PATH}:/downloads
networks:
- media
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS http://localhost:7878/ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
restart: unless-stopped
sonarr:
@@ -83,6 +109,14 @@ services:
- ${DOWNLOADS_PATH}:/downloads
networks:
- media
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS http://localhost:8989/ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
restart: unless-stopped
prowlarr:
@@ -96,6 +130,14 @@ services:
- /volume2/docker/prowlarr:/config
networks:
- media
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS http://localhost:9696/ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
restart: unless-stopped
bazarr:
@@ -111,6 +153,14 @@ services:
- ${TV_PATH}:/tv
networks:
- media
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS http://localhost:6767/"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
restart: unless-stopped
caddy:
@@ -136,6 +186,35 @@ services:
- prowlarr
- bazarr
- gluetun
+ labels:
+ - "autoheal=true"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS http://localhost:2019/config/"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
+ restart: unless-stopped
+
+ autoheal:
+ image: willfarrell/autoheal:latest
+ container_name: autoheal
+ environment:
+ - AUTOHEAL_CONTAINER_LABEL=autoheal
+ - AUTOHEAL_INTERVAL=10
+ - TZ=${TZ}
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ restart: unless-stopped
+
+ uptime-kuma:
+ image: louislam/uptime-kuma:1
+ container_name: uptime-kuma
+ volumes:
+ - uptime_kuma_data:/app/data
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ networks:
+ - media
restart: unless-stopped
networks:
@@ -145,3 +224,4 @@ networks:
volumes:
caddy_data:
caddy_config:
+ uptime_kuma_data: