Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose
| 🎧 **Audiobookshelf** | A self-hosted audiobook and podcast server with multi-user support and playback syncing. | [Details](services/audiobookshelf) |
| 🎥 **Bazarr** | A companion tool to Radarr and Sonarr for managing subtitles. | [Details](services/bazarr) |
| 📚 **BookLore** | A self-hosted application for managing and reading books. | [Details](services/booklore) |
| 📰 **FreshRSS** | A customizable feed reader with themes, extensions, and no separate database. | [Details](services/freshrss) |
| 🎥 **Frigate** | A self-hosted NVR with real-time AI object detection for IP cameras and local video monitoring. | [Details](services/frigate) |
| 🎮 **Hytale** | A self-hosted Hytale game server. | [Details](services/hytale) |
| 🖼️ **Immich** | A self-hosted Google Photos alternative with face recognition and mobile sync. | [Details](services/immich) |
Expand Down
30 changes: 30 additions & 0 deletions services/freshrss/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#version=1.1
#URL=https://github.com/tailscale-dev/ScaleTail
#COMPOSE_PROJECT_NAME= # Optional: only use when running multiple deployments on the same infrastructure.

# Service Configuration
SERVICE=freshrss
IMAGE_URL=freshrss/freshrss:latest

# Network Configuration
SERVICEPORT=80
DNS_SERVER=9.9.9.9

# Tailscale Configuration
TS_AUTHKEY=

# Time Zone setting for containers
TZ=Europe/Amsterdam # See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TAILNET_NAME= # Include the ".ts.net" if it is part of your Tailnet name

# FreshRSS Configuration
# You must set these before the first start. They are only applied while the data
# volume is empty; changing them later has no effect (change credentials in the UI instead).
# Avoid $, backticks and backslashes here: Compose and the FreshRSS entrypoint both interpret them.
ADMIN_USERNAME=admin
ADMIN_PASSWORD=
ADMIN_API_PASSWORD=
ADMIN_EMAIL=

# Crontab minute field for the built-in feed refresh. Leave empty to disable the internal cron.
CRON_MIN=3,33
51 changes: 51 additions & 0 deletions services/freshrss/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# FreshRSS with Tailscale Sidecar Configuration

This Docker Compose configuration sets up [FreshRSS](https://freshrss.org/) with Tailscale as a sidecar container, enabling secure access to your self-hosted feed reader over a private Tailscale network. With this setup, your FreshRSS instance remains fully private and accessible only from devices on your Tailnet, over HTTPS.

## FreshRSS

[FreshRSS](https://github.com/FreshRSS/FreshRSS) is a self-hosted RSS and Atom feed aggregator. It is lightweight, powerful, and customizable through themes and extensions. It exposes a Google Reader-compatible and Fever-compatible API, so clients such as Reeder, NetNewsWire, Unread, and FeedMe can sync against your own server.

## Key Features

- **Self-Hosted Feed Reading** – Your subscriptions, read state, and article archive stay on your own hardware.
- **Third-Party Client Sync** – Google Reader and Fever compatible APIs let third-party apps sync with your instance.
- **Extensible** – A large catalog of community themes and extensions.
- **Built-in Refresh Cron** – Feeds can be refreshed on a schedule inside the container, no host cron required.
- **Low Resource Usage** – Runs comfortably on a Raspberry Pi with the default SQLite database.
- **Private by Default with Tailscale** – No public exposure, no reverse proxies or port forwarding, and HTTPS handled by Tailscale Serve.

## Configuration Overview

In this deployment, the `tailscale-freshrss` service runs the Tailscale client and joins your Tailnet as the host `freshrss`. The `app-freshrss` service uses `network_mode: service:tailscale`. That means both containers share one network namespace. Tailscale Serve terminates HTTPS on port 443 and proxies to FreshRSS on `127.0.0.1:80` inside that shared namespace.

## Prerequisites

- Docker and the Compose plugin, with your user in the `docker` group (or use `sudo`).
- `/dev/net/tun` available on the host and the `NET_ADMIN` capability, both already declared in `compose.yaml`.
- A Tailscale [auth key](https://console.tailscale.com/admin/settings/keys) from the web admin console (**Settings → Keys → Generate auth key**). Set it to "Pre-Approved" if that option appears. The key is used only for the initial registration — with `TS_AUTH_ONCE=true` and the persisted `ts/state` volume, restarts reuse the stored node state — so a single-use key is sufficient. Tagging the device disables key expiry, which avoids re-authentication after the default 180 days.
- HTTPS certificates [enabled for your Tailnet](https://console.tailscale.com/admin/dns) (**DNS → HTTPS Certificates**). Tailscale Serve cannot issue a certificate without it, and the container will start but never serve.

## Files to check

Please verify the following files and variables before deploying:

- `.env` — set `TS_AUTHKEY`, `TAILNET_NAME`, `TZ`, `ADMIN_USERNAME`, `ADMIN_PASSWORD`, `ADMIN_API_PASSWORD`, and `ADMIN_EMAIL`.
- `compose.yaml` — confirm the volume paths and the `Proxy` port in the `ts-serve` config.

## Usage Notes

- **First run only.** `FRESHRSS_INSTALL` and `FRESHRSS_USER` drive FreshRSS's unattended installer, and only take effect while the data directory is empty. On later starts the entrypoint reports `FreshRSS already installed; no change performed.` and ignores `.env`. Set the passwords before the first `docker compose up` and change them from the FreshRSS UI afterwards, not by editing `.env`. Avoid `$`, backticks, and backslashes in those first-run values — Compose and the entrypoint both interpret them.
- **`TAILNET_NAME` feeds the base URL.** `compose.yaml` builds `--base-url` as `https://${SERVICE}.${TAILNET_NAME}`, so include the `.ts.net` suffix. FreshRSS displays the value read-only under **Configuration → System**; to change it after the first run, use `docker compose exec application ./cli/reconfigure.php --base-url https://freshrss.example.ts.net`.
- **Health check.** The app health check runs `./cli/health.php`, which ships with the image and requests `/api/`. Disabling the API in the UI will mark the container unhealthy even though the web interface works.
- **Ports.** The `ports` block stays commented out; the Tailnet is the only way in. Uncommenting it publishes plain HTTP on the host and bypasses Tailscale entirely. Note `SERVICEPORT` is `80`, which often collides on the host.
- **MagicDNS.** Uncomment `TS_ACCEPT_DNS=true` only if the container itself must resolve other MagicDNS names, such as an external database. It is not needed for the default SQLite setup.

## References

- [FreshRSS website](https://freshrss.org/)
- [FreshRSS on GitHub](https://github.com/FreshRSS/FreshRSS)
- [FreshRSS Docker documentation](https://github.com/FreshRSS/FreshRSS/blob/edge/Docker/README.md)
- [FreshRSS extensions](https://github.com/FreshRSS/Extensions)
- [Tailscale Serve documentation](https://tailscale.com/kb/1242/tailscale-serve)
- [Tailscale auth keys](https://tailscale.com/kb/1085/auth-keys)
74 changes: 74 additions & 0 deletions services/freshrss/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
configs:
ts-serve:
content: |
{"TCP":{"443":{"HTTPS":true}},
"Web":{"$${TS_CERT_DOMAIN}:443":
{"Handlers":{"/":
{"Proxy":"http://127.0.0.1:80"}}}},
"AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}}

services:
# Make sure you have updated/checked the .env file with the correct variables.
# All the ${ xx } need to be defined there.
# Tailscale Sidecar Configuration
tailscale:
image: tailscale/tailscale:latest # Image to be used
container_name: tailscale-${SERVICE} # Name for local container management
hostname: ${SERVICE} # Name used within your Tailscale environment
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required
- TS_USERSPACE=false
- TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz"
- TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The <addr>:<port> for the healthz endpoint
#- TS_ACCEPT_DNS=true # Uncomment when using MagicDNS
- TS_AUTH_ONCE=true
configs:
- source: ts-serve
target: /config/serve.json
volumes:
- ./config:/config # Config folder used to store Tailscale files - you may need to change the path
- ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path
devices:
- /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work
cap_add:
- net_admin # Tailscale requirement
#ports:
# - 0.0.0.0:${SERVICEPORT}:${SERVICEPORT} # Binding port ${SERVICE}PORT to the local network - may be removed if only exposure to your Tailnet is required
# If any DNS issues arise, use your preferred DNS provider by uncommenting the config below
#dns:
# - ${DNS_SERVER}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 10s # Time to wait before starting health checks
restart: always

# ${SERVICE}
application:
image: ${IMAGE_URL} # Image to be used
network_mode: service:tailscale # Sidecar configuration to route ${SERVICE} through Tailscale
container_name: app-${SERVICE} # Name for local container management
environment: # Variables are declared in the .env file.
- TZ=${TZ}
- CRON_MIN=${CRON_MIN} # Crontab minute field for the built-in feed refresh; leave empty in .env to disable
- TRUSTED_PROXY=127.0.0.1 # Tailscale Serve reaches FreshRSS over the shared loopback, so trust it for X-Forwarded-For
# The two variables below are only read on the very first start (empty data volume).
- FRESHRSS_INSTALL=--api-enabled --base-url https://${SERVICE}.${TAILNET_NAME} --db-type sqlite --default-user ${ADMIN_USERNAME} --language en
- FRESHRSS_USER=--api-password ${ADMIN_API_PASSWORD} --email ${ADMIN_EMAIL} --language en --password ${ADMIN_PASSWORD} --user ${ADMIN_USERNAME}
volumes:
- ./${SERVICE}-data/app/data:/var/www/FreshRSS/data # Configuration, SQLite database, feeds and articles
- ./${SERVICE}-data/app/extensions:/var/www/FreshRSS/extensions # Optional third-party extensions
depends_on:
tailscale:
condition: service_healthy
healthcheck:
test: ["CMD", "php", "-f", "./cli/health.php"] # Ships with the image; requires the API to stay enabled
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 30s # Time to wait before starting health checks
restart: always