Analytics cookies only (Google Analytics, PostHog). No ad networks, no cross-site tracking. See our privacy policy.
A self-hosted YouTube archive: subscribe to channels, download videos and metadata with yt-dlp, index them in Elasticsearch, then search, play and track what you have watched from a web UI, with Jellyfin and Plex plugins and a browser extension. GPL-3.0, Python (Django) with a React front end; it runs as three containers: the app, Redis and Elasticsearch.
Runs on a VPS from $20/mo (Vultr 4 GB, 4 GB RAM) · prices checked Oct 2026
Pick Tube Archivist when your YouTube subscriptions are a collection you want to keep: it subscribes to channels and playlists, downloads videos and their metadata with yt-dlp on a schedule, indexes everything in Elasticsearch so you can search across your archive, and plays it back in a web UI that tracks watched and unwatched, with Jellyfin and Plex plugins and a browser extension on the side. It is GPL-3.0 Python (Django) with a React front end, and it is a three-container stack: the app, Redis for queues and Elasticsearch for the index. Our snippet is the repo's `docker-compose.yml` at the pinned release with all three images pinned, the two example passwords replaced by generated ones, and the `vm.max_map_count` sysctl the README lists under common errors. Upstream's README is explicit about memory: about 2 GB for a small testing setup and about 4 GB for a mid to large one, so 2 GB is our floor. The catalog's PeerTube and Owncast are for publishing your own video; Tube Archivist is for archiving someone else's.
Paid link — we earn a commission if you shop through it.
Run these commands on your server:
# Tube Archivist — YouTube archive: subscribe, download with yt-dlp, index and play — based on the docker-compose.yml in the Tube Archivist repo at the pinned tag, pinned to v0.5.12
# Elasticsearch refuses to start unless the host kernel allows at least 262144 memory-map areas (README, "Common Errors")
sudo sysctl -w vm.max_map_count=262144
echo "TA_PASSWORD=$(openssl rand -hex 12)" > .env
echo "ELASTIC_PASSWORD=$(openssl rand -hex 16)" >> .env
cat > docker-compose.yml <<'EOF'
services:
tubearchivist:
image: bbilly1/tubearchivist:v0.5.12
restart: unless-stopped
ports:
- 8000:8000
volumes:
- media:/youtube
- cache:/cache
environment:
- ES_URL=http://archivist-es:9200
- REDIS_CON=redis://archivist-redis:6379
- HOST_UID=1000
- HOST_GID=1000
- TA_HOST=http://SERVER_IP:8000
- TA_USERNAME=tubearchivist
- TA_PASSWORD=${TA_PASSWORD}
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- TZ=UTC
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health/"]
interval: 2m
timeout: 10s
retries: 3
start_period: 30s
depends_on:
- archivist-es
- archivist-redis
archivist-redis:
image: redis:8.10.2
restart: unless-stopped
expose:
- "6379"
volumes:
- redis:/data
depends_on:
- archivist-es
archivist-es:
image: bbilly1/tubearchivist-es:8.19.0
restart: unless-stopped
environment:
- "ELASTIC_PASSWORD=${ELASTIC_PASSWORD}"
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- "xpack.security.enabled=true"
- "discovery.type=single-node"
- "path.repo=/usr/share/elasticsearch/data/snapshot"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es:/usr/share/elasticsearch/data
expose:
- "9200"
volumes:
media:
cache:
redis:
es:
EOF
docker compose up -d
timeout 300 bash -c 'until curl -fsS http://localhost:8000/ >/dev/null; do sleep 5; done'
# → http://SERVER_IP:8000 — sign in as tubearchivist with TA_PASSWORD from .env; TA_HOST must match the URL you typeThe README, the compose file and the environment-variable docs flag these before you start downloading: