jarvis2f/telegram-files: a self-hosted Telegram downloader that keeps running
A self-hosted Telegram file downloader for continuous, stable, and unattended downloads.
At a glance
- What is it?
- A Java and Next.js service that logs into Telegram with your own API credentials, queues channel and group files, and downloads them unattended. The Docker path is short; the security bootstrap is the part worth reading before you expose it.
- Who is it for?
- Adopt it if you already run Docker on a Linux host and want channel or group files pulled down without a desktop client open. Skip it if you only need a one-off download, or if you cannot give it a private-LAN first start and a reverse proxy that preserves X-Real-IP, X-Forwarded-Host and X-Forwarded-Proto.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap jarvis2f/telegram-files fills: unattended downloads from channels and groups
Desktop and mobile Telegram clients are built for a person watching a progress bar. Close the window, sleep the machine, or lose the session and the transfer stops. jarvis2f/telegram-files takes the opposite position. It is a server process that authenticates with your own Telegram API credentials, watches channels and groups, and writes files to a directory you control. The README describes it as "a self-hosted Telegram file downloader for continuous, stable, and unattended downloads", and the repository topics line up with that: docker, downloader, self-hosted, tdlib, telegram, unraid.
The audience is narrow and specific. You run a home server, a NAS or a small VPS. You already have Docker. You have Telegram channels or groups whose files you want on local disk, and you would rather not babysit a client. The project also lists multiple Telegram accounts, pause and resume, automatic transfer to designated destinations, and fetching files from Telegram shared links. Those are server-side conveniences, not client features.
It is not a general Telegram client, and it is not a way to browse Telegram from a browser tab. Everything it does is oriented around getting bytes out of Telegram and onto storage you own.
How the Java API, TDLib and the Next.js UI fit together
The repository splits into api/ (Java, built with Gradle, JDK23) and web/ (TypeScript, built with npm). TDLib sits underneath as the Telegram protocol layer, which is why the Dockerfile copies ./tdlib/linux_$TARGETARCH into /app/tdlib and why BUILD_TDLIB.md exists at the top level. The image builds a trimmed JRE with jlink from a dependencies.txt manifest, so the final Alpine layer carries only the modules the API needs.
Serving is handled by nginx inside the same container. The Dockerfile sets NGINX_PORT=80 and templates nginx.conf.template through entrypoint.sh. That means the Java process and the static web build are not separate services you wire together; one container exposes one port. The published Docker example maps 6543:80, so the host port is yours to choose while nginx stays on 80 internally.
State lives under APP_ROOT, defaulting to /app/data. The .env.example states the default database is sqlite at $APP_ROOT/data.db, with PostgreSQL and MySQL available through DB_TYPE, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD and DB_NAME. In docker-compose.yaml those database services are declared with required: false, so the stack starts without them. Persistence is therefore a single mounted volume unless you deliberately move to an external database.
The optional shared-node module is inert unless SHARE_ENABLED is true. When enabled it needs SEED_PLATFORM_URL (the README notes production accepts HTTPS only) and a SECRET_STORE_MASTER_KEY, described as a Base64-encoded 32-byte key. SHARED_ROOT defaults to $APP_ROOT/shared and the example warns it must not overlap $APP_ROOT/account. A qBittorrent integration is also present but commented out in .env.example, and the file says qBittorrent 5.1.2+ with WebAPI 2.11.4+ is required when QBITTORRENT_URL is set.
Installing jarvis2f/telegram-files with Docker and creating the first administrator
Before any of this, the README says you should apply for a Telegram api id and hash on the Telegram API page at my.telegram.org/apps. Without TELEGRAM_API_ID and TELEGRAM_API_HASH the container has nothing to authenticate with.
The recommended path is the interactive deploy script, which the README lists as supporting Linux amd64 and arm64. It handles install, configure, stop and update:
curl -fsSL https://raw.githubusercontent.com/jarvis2f/telegram-files/main/scripts/deploy.sh | bashThe README also documents running subcommands directly, including start, stop, update, restart, status, logs and config. If you would rather not pipe a script into a shell, the plain Docker invocation is the same shape. Note the port mapping and the volume:
docker run -d \
--name telegram-files \
--restart always \
-e APP_ENV=${APP_ENV:-prod} \
-e APP_ROOT=${APP_ROOT:-/app/data} \
-e HTTP_SECURE_COOKIES=${HTTP_SECURE_COOKIES:-false} \
-e TELEGRAM_API_ID=${TELEGRAM_API_ID} \
-e TELEGRAM_API_HASH=${TELEGRAM_API_HASH} \
-p 6543:80 \
-v ./data:/app/data \
ghcr.io/jarvis2f/telegram-files:latestThe compose route is to copy docker-compose.yaml and .env.example into your project directory and run docker-compose up -d. On unRaid, the README says to install from the Community Repositories by searching for telegram-files.
The step people skip is the bootstrap. On first start the API prints a 15-minute one-time bootstrap code. The README instructs you to open the UI from loopback or the same private LAN and create the first administrator before exposing the service, because the bootstrap is accepted only from loopback or private LAN source addresses. If you lose the password, recovery is local-only and revokes every active session:
java -cp api/build/libs/telegram-files.jar telegram.files.Maintain admin reset-password owner
java -cp api/build/libs/telegram-files.jar telegram.files.Maintain admin apply-reset ownerInside the container the same entry point is wrapped as tfm, which runs telegram.files.Maintain with -Djava.library.path=/app/tdlib. For source builds, the README's order is git clone, cd telegram-files, then npm install in web/ and gradle build in api/.
The security work you inherit by self-hosting this
This project puts management APIs, file previews and WebSockets behind an administrator session, and the README's security note is blunt about the order of operations: use HTTPS, configure HTTP_ALLOWED_ORIGINS, and complete the first-administrator bootstrap before exposing the service. A reverse proxy must preserve X-Real-IP, X-Forwarded-Host and X-Forwarded-Proto. The bundled nginx configuration already does this, but only if you use it or replicate it.
That requirement is not decorative. The bootstrap is gated on source address, so a proxy that rewrites or drops X-Real-IP can break the one-time setup, and a proxy that forwards the wrong scheme can leave secure cookies misconfigured. HTTP_SECURE_COOKIES defaults to false, and .env.example says to keep it true when serving through HTTPS. Get that pair wrong in either direction and you either lose login or send session cookies over plaintext.
There are rate limits to tune rather than ignore. .env.example exposes AUTH_LOGIN_ATTEMPTS_PER_MINUTE (10), FILE_READS_PER_MINUTE (120), HTTP_BODY_LIMIT_BYTES (1048576) and HTTP_ALLOWED_ORIGINS. The defaults are reasonable starting points, but they are per-deployment numbers, not universal ones. A shared instance serving many users will hit FILE_READS_PER_MINUTE quickly.
The honest framing is that this is an internet-facing service holding Telegram account credentials and a session store. The README documents the bootstrap and the recovery commands; it does not document a hardened deployment checklist beyond the origin and proxy headers. If you cannot put it behind TLS with a proxy you understand, run it on a LAN only.
Where jarvis2f/telegram-files is the wrong tool
If you need one file right now, this is the wrong instrument. Standing up Docker, applying for API credentials, bootstrapping an admin account and configuring a proxy is far more work than downloading in the client you already have open.
The project is also not a Telegram client replacement. It does not give you chats, and it does not let you send anything. It is a download pipeline with a web UI on top.
Resource expectations are real. The Dockerfile builds a custom JRE with jlink and ships TDLib native libraries for the target architecture, so the image is not a small static binary. TDLib also keeps its own session state, and the .env.example warns that SHARED_ROOT must not overlap $APP_ROOT/account, which tells you the account directory is something you should not touch or share.
The shared-node module deserves caution. It is inert by default, requires SHARE_ENABLED=true, a SEED_PLATFORM_URL that must be HTTPS in production, and a SECRET_STORE_MASTER_KEY. The key is generated with openssl rand -base64 32 and, per the comment, supplied by the deployment secret manager. If you enable sharing without treating that key as a real secret, you have added an attack surface for no benefit.
Finally, the README does not document rollback or downgrade steps between releases. If you pin latest and a release changes the schema or the account directory layout, the documented path back is not in the README.
How it differs from running gallery-dl or yt-dlp against Telegram
The closest general-purpose alternative is a command-line extractor such as gallery-dl or yt-dlp driven by a cron job. The difference is architectural, not cosmetic. Those tools are stateless invocations: you point them at a URL or a config, they fetch, they exit. Scheduling, retry policy, deduplication and progress tracking are things you build around them.
jarvis2f/telegram-files keeps a long-lived authenticated Telegram session through TDLib, holds a database of what it has seen and downloaded, and exposes a UI and API for pausing, resuming and reviewing. The README lists pause and resume, download statistics and reports, and rules-based automatic downloading as roadmap items that are checked off. That state is what makes unattended operation practical and also what makes the deployment heavier: a database, a volume, a session directory, and a web tier.
A second difference is the interface. A CLI extractor fits a script and returns text. This project expects a browser, an administrator account, and a proxy if it is not on a private network. If your workflow is already shell scripts and you do not want a web service, the CLI route is simpler and you give up the queue and the UI rather than the downloading itself.
Editorial conclusion
Adopt it if you already run Docker on a Linux host and want channel or group files pulled down without a desktop client open. Skip it if you only need a one-off download, or if you cannot give it a private-LAN first start and a reverse proxy that preserves X-Real-IP, X-Forwarded-Host and X-Forwarded-Proto. Verify first that you have a Telegram API id and hash from my.telegram.org, that your volume is writable by the PUID and PGID you set, and that HTTP_SECURE_COOKIES matches whether you terminate TLS in front of it.
Frequently asked questions
How do I install jarvis2f/telegram-files with Docker?
The README recommends the interactive deploy script for Linux amd64 and arm64, or a plain docker run that maps a host port to container port 80 and mounts a volume at /app/data. You must supply TELEGRAM_API_ID and TELEGRAM_API_HASH from my.telegram.org/apps. A docker-compose.yaml and .env.example are also provided to copy and run with docker-compose up -d.
How do I access jarvis2f/telegram-files on a PC or phone?
It serves a responsive web UI, and the README states it supports PWA with offline capabilities, so you reach it through a browser at the host and port you mapped. On first start the API prints a 15-minute one-time bootstrap code, and the README says to open the UI from loopback or the same private LAN to create the first administrator before exposing the service.
Where does jarvis2f/telegram-files store downloaded files?
Under APP_ROOT, which defaults to /app/data and is the mounted volume in the Docker examples. The .env.example notes the default database is sqlite at $APP_ROOT/data.db, and the shared-node module's SHARED_ROOT defaults to $APP_ROOT/shared and must not overlap $APP_ROOT/account.
How do I recover a lost administrator password in jarvis2f/telegram-files?
The README states password recovery is local-only and revokes every active session, using two Maintain commands: admin reset-password owner followed by admin apply-reset owner. Inside the container the same entry point is wrapped as tfm, which runs telegram.files.Maintain with the TDLib library path set.
What is the Telegram file size limit for downloads through jarvis2f/telegram-files?
The README and the repository files do not state a file size limit, so there is no documented number to give. The project relies on TDLib for the Telegram protocol layer, and any limit would come from Telegram rather than from a value configured in .env.example.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jarvis2f-telegram-files)