Self-hosted Youtube subscription follower and notification system
  • Rust 46.3%
  • CSS 33.6%
  • HTML 18.3%
  • Dockerfile 1.8%
Find a file
Kamil Pomykała 810b149a8e
Some checks failed
Security scan / Docker Scout CVE scan (push) Has been cancelled
docs: Update README to include logo and improve header alignment
- Added a logo image for better branding.
- Centered the header for improved visual appeal.
2026-03-28 18:24:58 +01:00
.github fix: Specify Dockerfile path in workflows for build and publish tasks 2026-03-28 17:24:23 +01:00
assets docs: Update README with new screenshot and formatting improvements 2026-03-28 16:13:34 +01:00
docker refactor: Reorganize Docker setup with improved structure and user permissions 2026-03-28 17:21:32 +01:00
migrations feat: Add unique index for channel_id in channels table 2026-03-27 14:24:45 +01:00
src feat: Add manual feed refresh functionality 2026-03-28 15:17:33 +01:00
templates feat: Update favicon and enhance navigation bar 2026-03-28 18:24:51 +01:00
.dockerignore feat: Add Docker configuration for application deployment 2026-03-27 00:40:45 +01:00
.gitignore chore: Update .gitignore to include .env file 2026-03-27 00:44:39 +01:00
AGENTS.md docs: Add AGENTS.md 2026-03-28 15:00:11 +01:00
Cargo.lock chore: Bump version to 1.0.1 for laneya package 2026-03-28 17:34:08 +01:00
Cargo.toml chore: Bump version to 1.0.1 for laneya package 2026-03-28 17:34:08 +01:00
CHANGELOG.md docs: Update CHANGELOG to remove unnecessary entries 2026-03-28 17:40:10 +01:00
cliff.toml docs: Update CHANGELOG to remove unnecessary entries 2026-03-28 17:40:10 +01:00
diesel.toml init: Initialize project structure and add core functionality 2026-03-26 21:35:25 +01:00
LICENSE feat: Add MIT License to the project 2026-03-26 21:44:41 +01:00
README.md docs: Update README to include logo and improve header alignment 2026-03-28 18:24:58 +01:00

Laneya logo

Laneya

Self-hosted YouTube subscription follower and notification system

Screenshot of the videos feed page

Name definition

lanéya - Quenya adverb. recently, not long ago

Element Gloss
la- "not, in-, un-; [ᴹQ.] none, not any"
néya "once, at one time"

Source


Features

  • Tracks YouTube channels via their public RSS feeds - no API key required
  • Real-time new-video notifications via WebSocket
  • Bulk import channels from a Google Takeout CSV export
  • Single-container Docker deployment with a persistent SQLite database
  • Built with Rust (Axum), HTMX and Tailwind CSS v4

Deploying with Docker

services:
  laneya:
    image: ghcr.io/akasiek/laneya:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - laneya_data:/data

    # If mounting data volume as a path on the host, ensure it has appropriate permissions for the container user.
    user: "${UID:-1000}:${GID:-1000}"

    environment:
      # Timezone for videos timestamps
      TZ: "UTC"
      # Set to true to skip YouTube Shorts
      FILTER_OUT_SHORTS: "true"
      # Number of videos to show per page
      VIDEOS_PER_PAGE: "24"
      # How often to fetch new videos from YouTube
      FEED_REFRESH_INTERVAL_MINS: "5"

    read_only: true
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL # Drop all Linux capabilities. The application doesn't need any special permissions.

volumes:
  laneya_data:

Save the above as compose.yaml, then run:

docker compose up -d

The app will be available at http://localhost:8080.

Data is stored in the laneya_data Docker volume which can be changed if needed.

Environment variables

Variable Default Description
HOST 0.0.0.0:8080 Address and port the server listens on
TZ UTC Timezone used for video timestamps
FILTER_OUT_SHORTS false Set to true to hide YouTube Shorts
VIDEOS_PER_PAGE 24 Number of videos displayed per page
FEED_REFRESH_INTERVAL_MINS 5 How often (in minutes) feeds are refreshed

Container security

  • The container runs as a non-root system user with no home directory and no login shell
  • The container filesystem is read-only - only the /data volume can be written to
  • All Linux capabilities are dropped
  • no-new-privileges is enforced - the process cannot gain elevated privileges via setuid/setgid binaries
  • Multi-stage Docker build - the runtime image contains only the compiled binary and static assets; no compiler, build tools, or source code

Adding channels

Single channel

You need the channel ID - a string starting with UC (e.g. UCXuqSBlHAE6Xw-yeJA0Tunw).

Way to find it:

  1. On the channel page click ...more
  2. Scroll to the very bottom of the modal and click Share channel
  3. Click Copy channel ID
  4. Paste the ID into the form on the Channels page and submit

Bulk import via Google Takeout

  1. Go to Google Takeout
  2. Deselect all products, then scroll down and select only YouTube and YouTube Music
  3. Click All Youtube data included and make sure only subscriptions is selected
  4. Click Next step, then choose your delivery method and export frequency, and click Create export. Export should take about a minute.
  5. Inside the downloaded archive find YouTube and YouTube Music/subscriptions/subscriptions.csv. This should be the only file in that archive.
  6. Upload that file using the Bulk Import form on the Channels page

Development

Prerequisites

Setup

.env file

Create a .env file in the project root with the following content:

DATABASE_URL=./database.db

Install dependencies and apply database migrations

# Install JS dependencies
cd templates && pnpm install && cd ..

# Apply database migrations
diesel migration run

Running the server

With hot-reload (restarts server on code changes):

systemfd --no-pid -s http::8080 -- cargo watch -x run

or without hot-reload:

cargo run

Tailwind CSS watch

In a separate terminal from the templates/ directory:

cd templates && pnpm run watch:css

Watches styles/styles.css and rebuilds styles/tailwind.css on every change.

Database migrations

# Apply pending migrations
diesel migration run

# Revert the last migration
diesel migration revert