K-Vault: a file hosting app that treats Telegram as just another backend
基于 Cloudflare 的 Serverless 聚合云盘。以 Telegram 为核心(支持 Webhook 直传与 2GB 扩展),并全面兼容 R2、S3、Discord 及 HuggingFace 等多存储后端。零成本构建你的全能私有数据金库。
At a glance
- What is it?
- K-Vault is a self-hosted file and image hosting service that runs either on Cloudflare Pages or as a Docker image, and its interesting design is that Telegram is only the first of seven storage backends rather than the whole product. It is written in JavaScript, licensed CC0-1.0, and it hands you an admin console, a WebDAV page and token-based API uploads on top of whatever storage you already pay for.
- Who is it for?
- Adopt K-Vault if you want a self-hosted upload front end and already have spare storage capacity in one of the seven supported backends, especially if you want Telegram's free channel storage or an S3 bucket sitting half empty.
- Can I use it commercially?
- Yes. CC0-1.0 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 32 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Telegram is the headline, but the architecture treats it as one backend of seven
K-Vault presents itself as a Cloudflare based serverless aggregate cloud drive, with Telegram at the core and Webhook direct upload with a 2GB extension, and it lists full compatibility with R2, S3, Discord, HuggingFace and other storage backends.
The feature list gives that claim its precise form. The multi-backend line names Telegram, Cloudflare R2, S3 compatible storage, Discord, HuggingFace, WebDAV and GitHub, and the storage category feature in the admin interface is described as visually separating files by backend. The Docker deployment section adds a note about which backends suit which mode: Pages is framed for Cloudflare free hosting, KV, R2 and Pages Functions, while Docker is framed for VPS, NAS and internal network deployment, and for long-term self-hosting of WebDAV, S3, GitHub and HuggingFace backends.
So the Telegram-first framing describes where the project started and what the free tier looks like, not an architectural commitment. That distinction is the first thing to understand, because it changes what you can and cannot do. A Telegram backend routes uploads through a bot into a channel, with a webhook reply carrying the direct link, and a signing option for the direct link that reduces KV read and write consumption. R2 and S3 give you an object store with its own durability and its own bill, and Discord and GitHub are the same idea in other services.
The environment configuration confirms the plurality. The default storage type accepts telegram, r2, s3, discord, huggingface, webdav or github, and the Docker section says you do not have to preconfigure any storage environment variables at all, since storage configurations can be added, tested and switched from the admin console after startup.
Two deployment modes that share the same routes but not the same runtime
The project keeps exactly two official deployment methods, and the document is unusually clear that they are different things rather than one being a subset of the other.
The first is Cloudflare Pages, static pages plus Pages Functions, which is the free tier, edge function, Cloudflare KV and R2 path. The second is Docker, using the single image ghcr.io/katelya77/k-vault:latest, aimed at VPS, NAS and internal network deployment.
Both expose the same entry points, and the set is short enough to be worth memorising. The upload page is at the root path, the admin console is /admin.html, the WebDAV page is /webdav.html, the admin and API endpoints are under /api/*, ordinary uploads go to POST /upload, API token uploads go to /api/v1/upload, and file direct links and shares live under /file/*, /share/* and /s/*.
The Docker image is worth a closer look because it explains how the two modes feel the same. It is built from node:22-alpine with nginx installed, and nginx sits in front of the Node API inside the container, so there is still only one port exposed to the outside. The Node API itself listens on 8787 while 8080 is exposed, and the healthcheck inside the Dockerfile polls /api/health on the 8080 port. The compose file repeats that healthcheck with wget and gives the Redis service a profiles entry, which means Redis does not start unless you ask for it.
The static pages are copied straight into the nginx html directory, index.html, admin.html, gallery.html, webdav.html, login.html, preview.html, block-img.html, whitelist-on.html, admin-imgtc.html and admin-waterfall.html, along with the css, js, svg, png and ico files. That is why the root-path UI works identically in both modes.
Running the Docker image in one command, and the keys it generates for you
The simplest deployment needs no clone and no local Node installation. Create a named volume, then run the image:
docker volume create kvault_data
docker run -d \
--name kvault \
--restart unless-stopped \
-p 8080:8080 \
-v kvault_data:/app/data \
ghcr.io/katelya77/k-vault:latestThe data volume is the one piece you must not skip, because it holds the SQLite database at /app/data/k-vault.db, the chunk directory at /app/data/chunks and the runtime secrets file. The compose file mounts the same volume, kvault_data, at /app/data, and names the image with a default override so you can pin a different tag with KVAULT_IMAGE.
Secrets are handled in a way that is worth knowing about. On first start, if CONFIG_ENCRYPTION_KEY and SESSION_SECRET are not provided, the container generates them and saves them to /app/data/runtime.env on the data volume. The documentation states it directly: deleting the container does not lose those keys, deleting the data volume does.
That is a sensible default and it has a corollary. The encryption key protects stored storage credentials, so losing the volume loses the ability to read the configuration that was already saved. If you care about the settings, treat the volume as the thing to back up, and consider setting both keys explicitly so they live in your own secret management rather than in a generated file.
The admin account is the other thing to set. The environment example ships BASIC_USER and BASIC_PASS, and the documentation recommends setting them explicitly for public deployments, which is the difference between an admin console nobody can reach and one anybody can try a password against.
The Pages path has a specific set of empty build fields, and a specific way to get it wrong
The Cloudflare Pages instructions are short but unforgiving, and the failure modes are documented because they are common.
You fork the repository, create a Pages project through the Cloudflare Dashboard by connecting to Git, and select the forked repository. The build settings table is the part people get wrong. Framework preset is None or no preset. Root directory is left empty, meaning the repository root. Install command is left empty. Build command is left empty. Build output directory is left empty. Deploy command is left empty.
All five empty. The documentation explains why with a warning: do not put npx wrangler deploy in the Pages Git integration, because that is the Workers deploy command, and filling it in produces an error along the lines of The detected framework ("Hono") cannot be automatically configured. There is a second warning that the Pages entry point is the repository root static pages plus the functions/ directory, so filling in npm run build or frontend/dist would deviate from the current root path UI deployment.
Then there is the KV binding, which the documentation calls required for image management, chunk tasks and basic configuration. You create a namespace, go back to the project settings, and bind it with the variable name img_url. That name is not free choice, the documentation says the variable name must be img_url.
At least one storage backend has to be configured through environment variables, and the Telegram example shows TG_Bot_Token and TG_Chat_ID as required, with BASIC_USER and BASIC_PASS optional. After changing environment variables or bindings you must redeploy, because a running deployment does not pick up new configuration. The check afterwards is the status endpoint: visit /api/status and confirm that telegram, kv and r2 report the state you expect.
Configuration that lives in a YAML example file, not just environment variables
The .env.example file is the clearest map of what the application can be told to do, and it is organised as commented sections rather than a flat list.
The runtime section sets NODE_ENV, PORT with a comment to keep it at 8787, and PUBLIC_BASE_URL for the external address used to build callback and webhook URLs. The admin section holds BASIC_USER, BASIC_PASS, SESSION_COOKIE_NAME and SESSION_DURATION_MS, which the example sets to 86400000.
The upload policy is split into two. Guest upload is off by default, with GUEST_MAX_FILE_SIZE at 5242880 and GUEST_DAILY_LIMIT at 10, and the guest upload feature is described as optional with size and daily count limits. Separately, UPLOAD_MAX_SIZE is 104857600, the 100MB figure quoted in the feature list, with UPLOAD_SMALL_FILE_THRESHOLD at 20971520 and CHUNK_SIZE at 5242880.
The storage backend section is where the seven options become concrete, and each has its own variable prefix: R2_ENDPOINT, R2_REGION set to auto, R2_BUCKET and the R2 keys, then S3_ENDPOINT, S3_REGION set to us-east-1, S3_BUCKET and the S3 keys, DISCORD_WEBHOOK_URL, DISCORD_BOT_TOKEN and DISCORD_CHANNEL_ID, HF_TOKEN and HF_REPO, then the WebDAV block with WEBDAV_BASE_URL, WEBDAV_USERNAME, WEBDAV_PASSWORD, WEBDAV_BEARER_TOKEN and WEBDAV_ROOT_PATH, and a GitHub block.
One option is worth isolating because it changes the architecture. SETTINGS_STORE accepts sqlite or redis, and the comment says to use sqlite by default, with a redis:// or rediss:// URL for Upstash, Redis or KVrocks, plus a prefix and a connect timeout. That is how the same Docker image serves both a single-node home server and a multi-node deployment sharing settings.
Verifying a deployment with a WebDAV smoke test instead of trusting the UI
The project includes a regression script and a documented pass condition, which is unusual for a self-hosted script collection and is the most useful operational advice in the documentation.
The recommendation is to run at least one WebDAV smoke test after deployment, to confirm the full loop of configuring the storage, uploading, downloading and deleting. The regression script is invoked with a base URL, admin credentials, a storage type and a JSON configuration blob, and the surrounding npm scripts cover the rest of the setup:
docker compose up -d
cp .env.example .envThe acceptance criteria are stated as three checks. The webdav.connected field in /api/status must be true. In a Docker self-hosted deployment you can additionally check that /api/storage/:id/test returns connected=true. And the upload, download and delete steps of the regression script must all pass.
There is a matching set of diagnostic scripts in package.json. pages:r2:doctor runs a Cloudflare Pages R2 diagnostic, docker:doctor runs a Docker storage diagnostic, docker:smoke:ci runs a container smoke test, and regression:storage runs the storage regression. The local development start command is also revealing: npx wrangler pages dev with the img_url KV namespace, an R2 bucket binding, a compatibility date, port 8080 and a persisted local data directory, which means the Pages runtime is what you develop against locally, not a separate mock.
This is the shape of a project that expects to be installed by someone who does not want to read the source, so it ships the verification tooling along with the code.
What the licence permits, and what the project does not tell you
The licence recorded for the repository is CC0-1.0, the public domain dedication, which is an unusual choice for an application and worth understanding rather than assuming.
CC0 places the work as far as possible into the public domain and waives rights to the extent possible. For an application rather than a content library, that means you can run it, modify it and redistribute it without attribution requirements. The practical risk is not legal but editorial: a project with a permissive public domain dedication and no vendor behind it gives you no support commitment, no security patches obligation, and no path for the maintainer to respond to a disclosure.
On maintenance, the facts are modest. The last push was on 2026-09-21, the repository is not archived, and there are no GitHub releases. Instead, the image is built automatically by GitHub Actions from the main branch and from tags, pushing a single image to ghcr.io/katelya77/k-vault, which means your version of record is the image tag rather than a release artefact. A README in English and one in Chinese are maintained in parallel, with the Chinese one as the primary in the repository and README-EN.md as the translation, and a separate README-DOCKER.md for the container path.
Two areas are simply not covered in the documentation, and both matter for a service that handles uploads from the internet. There is no documented rate limiting beyond the per-guest daily count, and no documented content scanning beyond an optional image moderation API that automatically blocks unsuitable images. There is also a block-img.html page and a whitelist-on.html page in the root, which suggests some filtering exists in the interface, but the mechanism behind them is not written down. Treat the moderation toggle as a filter, not as a safeguard, and put your own controls in front of the container if the uploads come from strangers.
Editorial conclusion
Adopt K-Vault if you want a self-hosted upload front end and already have spare storage capacity in one of the seven supported backends, especially if you want Telegram's free channel storage or an S3 bucket sitting half empty. Do not adopt it expecting a hardened file service, since guest uploads, public file links and a Telegram bot token all widen the blast radius of a mistake, and the split between Pages and Docker deployment means your configuration behaviour differs by mode. Before you deploy, set BASIC_USER and BASIC_PASS, confirm the KV binding named img_url exists if you use Pages, run the WebDAV smoke test against your own instance, and read the LICENSE file to see what the CC0 grant actually covers for your use.
Frequently asked questions
How do I deploy K-Vault with Docker?
Create a named volume and run the single image ghcr.io/katelya77/k-vault:latest with port 8080 published and the volume mounted at /app/data, or run docker compose up -d from a clone. The upload page is at the root path, the admin console at /admin.html, the WebDAV page at /webdav.html and the health check at /api/health.
Which storage backends does K-Vault support?
Telegram, Cloudflare R2, S3 compatible object storage, Discord, HuggingFace, WebDAV and GitHub. The default storage type is chosen with DEFAULT_STORAGE_TYPE, and storage configurations can also be added, edited, tested and switched from the admin console after startup.
What happens to my keys if I delete the Docker container?
Nothing. If CONFIG_ENCRYPTION_KEY and SESSION_SECRET are not provided, the container generates them on first start and saves them to /app/data/runtime.env on the data volume, so deleting the container keeps them and deleting the data volume loses them.
Which Cloudflare KV binding name does K-Vault require?
The KV namespace must be bound with the variable name img_url. The documentation states the variable name must be img_url, and the binding is required for image management, chunk tasks and basic configuration.
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/katelya77-k-vault)Was this useful?