Webtor self-hosted: running a torrent streaming server in one container
Self-hosted version of webtor.io implemented as an all-in-one Docker image
At a glance
- What is it?
- The self-hosted build of webtor.io packages a dozen backend services into a single Docker image so you can stream or download files out of a torrent, keep a personal library and embed a player, all on your own machine. The interesting parts are the security model, the API that is deliberately not public, and the way the image is assembled from pinned component images.
- Who is it for?
- Adopt Webtor self-hosted if you want a single container that turns your own torrent collection into a streamable library with a web player, subtitles and a Stremio addon, and if you are comfortable operating it on your own hardware. Do not adopt it if you need the public webtor.io service behaviour unchanged, since the self-hosted build deliberately differs on authentication and API exposure, and it is not the right tool if you cannot give the instance a domain and https.
- 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 4 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One container hides a dozen separate backend services
The self-hosted version of webtor.io is delivered as one Docker image, and the interesting engineering is in what that image actually contains. The Dockerfile does not compile anything, as its own comments state. Instead it assembles a set of prebuilt component images as build stages: torrent-store, magnet2torrent, external-proxy, torrent-web-seeder, torrent-web-seeder-cleaner, content-transcoder, content-prober, torrent-archiver, srt2vtt and subtitle-translate, among others.
Each of those component images is referenced by tag and by digest at the same time, so the resulting image is reproducible and each component's provenance can be traced. A renovate.json in the repository root is what drives those bumps one at a time. The base is Alpine, declared as ARG ALPINE_VER="3.22", with s6-overlay 3.2.0.2 supplying the process supervisor, which is why the repository has both an s6-overlay directory and a cont-init.d directory for service startup scripts.
One gap is stated in the Dockerfile comments rather than hidden: the Alpine base and its apk packages are not pinned to a digest and will float to whatever apk resolves at build time. So the components are immutable while the userspace underneath them is not. The primary language of the repository is Shell, which matches a project whose contribution surface is orchestration rather than application code.
Running the whole thing is one command, and the three named volumes are what persist between runs:
docker run -d -p 8080:8080 -v data:/data -v pgdata:/pgdata -v storage:/storage --name webtor --restart=always ghcr.io/webtor-io/self-hosted:latestThe image is published for linux/amd64 and linux/arm64, so it runs on x86 servers, Apple Silicon, 64-bit ARM boards and ARM NAS devices, with Docker picking the right variant. 32-bit ARM, armv7, is not supported.
An open instance by default, and the password that closes it
The default posture of a fresh container is wide open. The documentation says it directly: by default the instance is open, and anyone who can reach it has full access. That includes adding torrents to the account library and reading whatever the instance can reach.
The fix is a password, set from the profile page or supplied at startup:
docker run -e ADMIN_PASSWORD=your-password -d -p 8080:8080 -v data:/data -v pgdata:/pgdata -v storage:/storage \
--name webtor --restart=always ghcr.io/webtor-io/self-hosted:latestADMIN_PASSWORD overrides whatever was set in the profile, which also makes it the way back in if you forget the password. The more interesting case is changing the password without putting it on a command line, because the container's own services run under an environment that a bare docker exec does not inherit. The documented incantation sources that environment first:
docker exec webtor sh -c 'set -a; . /etc/webtor/common.env; cd /app/web-ui && ./web-ui admin set-password <new-password>'The reason given is that web-ui is not started under that environment by docker exec, so a bare invocation connects to Postgres with the wrong defaults, PG_USER=webhook and PG_DATABASE=webhook with no password, instead of the app/app role the embedded database actually created. This is a small detail that will cost you an afternoon if you skip it.
Once a password exists, EVERY page requires authentication by default, because ONLY_AUTHORIZED=true is the default. A visitor without a valid session is redirected to /login rather than getting read access. Setting ONLY_AUTHORIZED=false restores the old behaviour, where pages stay reachable without logging in and only actions that were already password gated, such as changing settings, still prompt for a password.
Embedding is a separate decision from logging in
The auth model looks like it should apply to the embedded player too, but it deliberately does not. A third party iframe has no session with your instance, so redirecting it to a login form would only render a broken player. Instead, which sites may embed is decided by the embed domain list on your profile page, and a site that is not on the list gets a short not authorized message inside the player.
Your own domain, localhost and 127.0.0.1 are always on the list. To let any site embed from your instance, set EMBED_ONLY_AUTHORIZED=false, which is a wide door and should be a deliberate choice.
There is a second condition that catches people out. Embedding on another site, and localhost and 127.0.0.1 count as different sites for this purpose, requires the instance to be served over https with DOMAIN set to the https address. Over plain http the browser keeps no session in a cross-site frame, so the player there cannot start at all.
That interacts with the session cookie in a way that produces a confusing symptom. With an https DOMAIN the session cookie is Secure, so opening the same instance over plain http://<ip>:8080 does not work, because the browser drops the cookie and every action answers CSRF token mismatch. The documentation is blunt about the related mistake: do not leave DOMAIN at an http value behind a reverse proxy, because links would point to that address, the cookie would go without the Secure flag, and cross site embedding would stay off.
The REST API is running but nginx refuses to proxy it
Access to the programmatic side changed shape, and the documentation is specific about it. The rest-api is not exposed publicly, because nginx no longer proxies /rest-api/. The service still runs inside the container, but only web-ui's own /api/v1 can reach it, and /api/v1 requires an API key.
The key comes from the profile page in a browser, which itself requires a login if ADMIN_PASSWORD or ONLY_AUTHORIZED are in effect. Calls then carry a bearer token:
curl -H "Authorization: Bearer <your-api-key>" http://localhost:8080/api/v1/resource/<id>The migration is one for one on the path suffix. /api/v1 mirrors the old /rest-api/ paths, for example POST /resource, GET /resource/<id>, GET /resource/<id>/list and GET /resource/<id>/export/<content_id>, and returns the same response shapes. Only the host path prefix and the authentication requirement changed.
If you were integrating against the old interface, this is the single most likely thing to break during an upgrade, and the fix is mechanical: swap the path prefix and add a bearer token.
DOMAIN decides your links, your cookie and your embed
The DOMAIN variable is the piece of configuration that has to be right, and the project explains it in three cases rather than one.
For plain http on your own network, such as http://192.168.1.10:8080 or http://nas.local:8080, set DOMAIN to exactly that address, and stream and download links are built from it:
docker run -e DOMAIN=http://192.168.1.10:8080 -d -p 8080:8080 -v data:/data -v pgdata:/pgdata -v storage:/storage --name webtor --restart=always ghcr.io/webtor-io/self-hosted:latestBehind an HTTPS reverse proxy, set DOMAIN=https://example.com and always open the instance at that https address:
docker run -e DOMAIN=https://example.com -d -p 8080:8080 -v data:/data -v pgdata:/pgdata -v storage:/storage --name webtor --restart=always ghcr.io/webtor-io/self-hosted:latestThe scheme is not cosmetic. It decides how the login session is kept, and the https variant is what makes cross site embedding work at all, because a browser keeps no session in a cross site frame over plain http.
A separate note covers the port. The published documentation section on custom ports exists because the container listens on 8080 internally while you may want to publish a different port on the host, in which case DOMAIN has to carry the host side address so the generated links match what the browser can actually reach.
What the instance actually does with your torrents
The feature set is what makes this worth containerising. Direct Download Link selects any file inside a torrent and downloads it directly, and the whole torrent can be downloaded as a ZIP archive on the fly with the original directory structure preserved, without a torrent client. Video and audio streaming start immediately without a download step first, with the video formats listed as avi, mkv, mp4, webm, m4v, ts and vob, and the audio formats as mp3, wav, ogg, flac and m4a.
The library is where it becomes a service rather than a one-off. You add torrents to your account and movies and series are detected automatically. Subtitles come from OpenSubtitles, which needs a free OpenSubtitles API key, and there is a separate AI subtitle feature that translates embedded, uploaded or OpenSubtitles tracks into your language on the fly in the player, which needs an Anthropic API key. Those two keys are optional, and the transcription, translation and probing work happens in the container through the content-transcoder, content-prober, srt2vtt and subtitle-translate components.
For TV, the Stremio integration works by installing the addon with the link from your profile and starting playback from your library on the television. For your own website, the embed SDK is the route, and it is the piece that the embed domain list exists to protect.
One hosting option is mentioned in passing: the instance can be run on ElfHosted, so this is not restricted to people who want to run Docker themselves.
A fast moving release line with a documented breaking change
The project is shipping steadily. The recent releases are v2.1.1 from 2026-10-01, v2.1.0 from 2026-09-18 and v2.0.10 from 2026-09-13, and the last push was on 2026-10-19. Three releases in a month is a fast cadence, and the API change described above is the kind of change that happens at that speed.
The licence is MIT, and the repository also carries a CLAUDE.md for automated coding assistants plus a tests directory, which is a reasonable sign that the project expects changes to be exercised.
The self-hosted build is not a drop-in replacement for the hosted service, and the documentation does not pretend otherwise. Authentication is on by default where the public site works differently, the public REST prefix no longer proxies, and embedding is gated by a domain list. If your plan was to point existing clients at the public API paths, you will be rewriting them to /api/v1 with a key.
For that reason the most useful first hour with this image is spent on configuration rather than on playback: set a password, set the domain, decide on the authorization mode, and only then add content.
Editorial conclusion
Adopt Webtor self-hosted if you want a single container that turns your own torrent collection into a streamable library with a web player, subtitles and a Stremio addon, and if you are comfortable operating it on your own hardware. Do not adopt it if you need the public webtor.io service behaviour unchanged, since the self-hosted build deliberately differs on authentication and API exposure, and it is not the right tool if you cannot give the instance a domain and https. Before you expose it, set ADMIN_PASSWORD, decide whether ONLY_AUTHORIZED should stay true, set DOMAIN to the exact address you will open in the browser, and confirm that the embed domain list covers every site you intend to serve a player on.
Frequently asked questions
How do I start a self-hosted Webtor instance?
Install Docker and run the published image with three named volumes for data, pgdata and storage, then open the interface at http://localhost:8080. The image is published for linux/amd64 and linux/arm64, and 32-bit ARM is not supported.
How do I protect a Webtor instance that is open to anyone by default?
Set a password from the profile page or start the container with ADMIN_PASSWORD, which overrides the profile password and is the way back in if you forget it. With a password set, EVERY page requires authentication because ONLY_AUTHORIZED=true is the default, and setting it to false restores unauthenticated read access.
Why is the Webtor REST API returning 404 for my old endpoints?
nginx no longer proxies /rest-api/, and the service is only reachable through web-ui's own /api/v1, which requires an API key generated on your profile page. The paths mirror the old ones one for one, so you only need to change the prefix and add a bearer token.
Can I embed the Webtor player on another website?
Yes, but the site has to be on the embed domain list in your profile page, and embedding on a different site needs the instance served over https with DOMAIN set to the https address, because browsers keep no session in a cross-site frame over plain http.
Which optional API keys does Webtor self-hosted need?
OpenSubtitles subtitle matching needs a free OpenSubtitles API key, and the AI subtitle translation feature needs an Anthropic API key. Both are optional and are configured separately from the container startup options.
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/webtor-io-self-hosted)Was this useful?