TorrenClou

Getting Started

Install TorrenClou with one command, then reach it, put it behind a proxy, or point it at a bigger disk.

Requirements

  • Docker, and a machine that can run Linux containers
  • About 2 GB of RAM
  • Enough disk for the torrents you intend to download — they land on a volume before being uploaded, and are deleted afterwards by default

Images are built for linux/amd64 and linux/arm64, so an ARM VPS or a Raspberry Pi works.

The one-liner

curl -fsSL https://raw.githubusercontent.com/TorrenClou/deploy/main/install.sh | bash

Windows, in PowerShell:

irm https://raw.githubusercontent.com/TorrenClou/deploy/main/install.ps1 | iex

It checks Docker is running, pulls the image, starts it, waits for it to become healthy, and prints the URL to open. There is no configuration step — no .env to fill in, no secrets to generate. The container creates its own on first boot and keeps them on its data volume.

Open the URL it prints and create your account.

If you would rather read the script before running it — a reasonable habit — it is install.sh, about 90 lines.

Manual install

The installer runs exactly this:

docker run -d \
    --name torrencloud \
    --restart unless-stopped \
    -p 47100:47100 \
    -p 47200:47200 \
    -p 47500:47500 \
    -p 47600:47600 \
    -v torrencloud-pgdata:/data/postgres \
    -v torrencloud-redis:/data/redis \
    -v torrencloud-downloads:/data/downloads \
    ghcr.io/torrenclou/torrentclou:latest

No -e flags and no --env-file. The container works out the rest.

If you only want the app, drop the -p 47500 and -p 47600 lines — those are Grafana and Prometheus, and the app does not need them. See Security for why publishing fewer ports is the better default.

Ports

PortService
47100Web interface
47200API, plus /hangfire
47500Grafana
47600Prometheus

Only 47100 needs to be reachable for normal use. The browser talks to the API through the frontend's same-origin proxy, so 47200 does not need to be open.

Reaching it

The installer prints an sslip.io address — a public DNS service that resolves 203-0-113-5.sslip.io to 203.0.113.5. That gives you a working hostname on a fresh server with no DNS set up. It is only a convenience for the printed link; the app works on whatever address you reach it by.

Open port 47100 in your firewall:

sudo ufw allow 47100/tcp

Custom domain

Point an A record at the server and open the app on it. Nothing to configure — the app derives its own address from the request.

Behind a reverse proxy

A minimal nginx server block:

server {
    listen 443 ssl;
    server_name torrents.example.com;

    # ... your certificate directives ...

    location / {
        proxy_pass http://127.0.0.1:47100;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-Host  $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;

        # Large uploads and long-lived progress streams
        client_max_body_size 100M;
        proxy_read_timeout   3600s;
    }
}

The X-Forwarded-Host and X-Forwarded-Proto headers matter: the app uses them to work out its own public address, which is what the Google Drive OAuth callback returns to. If your proxy cannot send them, set the address explicitly instead:

docker run -d ... \
    -e PUBLIC_FRONTEND_URL=https://torrents.example.com \
    -e NEXTAUTH_URL=https://torrents.example.com \
    ghcr.io/torrenclou/torrentclou:latest

Traefik and Caddy both send those headers by default.

Storing downloads on a specific disk

Bind-mount a path instead of using the named volume:

-v /mnt/big-disk/torrents:/data/downloads

Torrents can be large, and this is the volume that grows.

Next steps

  • First-run setup — create your account and connect storage
  • Usage guide — adding torrents and watching transfers
  • Security — read this before exposing an instance to the internet
  • Updating — upgrades, backups and rollbacks

On this page