ipMagnetize is a fork of ipMagnet, which shows which IP addresses your BitTorrent client hands out to trackers. See upstream for what the app does, its privacy notes, and manual (non-Docker) setup.
This fork adds:
- A Docker image configured through environment variables, published at
ghcr.io/nkboedecker/ipmagnetizeforlinux/amd64andlinux/arm64. - An optional HTTPS setup using SWAG with Let's Encrypt certificates.
- Correct client IP detection behind a reverse proxy (
TRUST_PROXY).
DISCLOSURE: This code was written with the assistance of Claude Code (Anthropic).
From a checkout of this repository:
docker compose up -dThis runs the published image (see Image tags). To build the image from your checkout
instead, add --build.
ipMagnetize is now on http://localhost/. Before exposing it publicly, set TRACKER_URL to the address
BitTorrent clients will reach it at (see Configuration), or the magnet links it
generates will point clients at localhost.
| Variable | Default | Set in | Purpose |
|---|---|---|---|
TRACKER_URL |
http://localhost/ |
override file | Public URL of this instance, with trailing slash. Embedded in magnet links as the tracker. |
ENABLE_INTERVAL |
false |
override file | Ask clients to re-announce periodically. Read upstream's warning before enabling. |
TRACKER_INTERVAL |
300 |
override file | Re-announce interval in seconds, if enabled. |
TRUST_PROXY |
false |
override file | Take the client IP from a reverse proxy's X-Real-IP header. The SWAG overlay sets this to true. See Behind another reverse proxy. |
IPMAGNETIZE_BIND |
0.0.0.0 |
.env |
Host address ipMagnetize's port is published on. |
IPMAGNETIZE_PORT |
80 |
.env |
Host port ipMagnetize is published on. |
IPMAGNETIZE_MEM_LIMIT |
256m |
.env |
Memory cap for the ipMagnetize container. It idles around 20 MB. |
IPMAGNETIZE_VERSION |
1 |
.env |
Tag of the published image to run. See Image tags. |
SWAG_URL |
(required for SWAG) | .env |
Your registered domain, e.g. example.com. |
SWAG_EMAIL |
(required for SWAG) | .env |
Contact address for Let's Encrypt. |
SWAG_TZ |
Etc/UTC |
.env |
Time zone for the SWAG container. |
SWAG_CONFIG_DIR |
./swag-config |
.env |
SWAG's persistent config: certificates, nginx config, and the Cloudflare token. |
SWAG_MEM_LIMIT |
512m |
.env |
Memory cap for the SWAG container. Raise it if SWAG also serves other apps. |
SWAG_VERSION |
5.8.0-ls486 |
.env |
SWAG image tag. See Updating. |
If TRACKER_URL contains ", $, \ or #, or TRACKER_INTERVAL isn't a whole number, the
container refuses to start. Run docker compose logs ipmagnetize to see why.
Don't edit docker-compose.yml. Your changes would conflict with (or be overwritten by) the next
git pull. Use these two files instead. Both are ignored by git.
-
docker-compose.override.ymlholds the container settings (TRACKER_URLand the others marked "override file" above):services: ipmagnetize: environment: TRACKER_URL: "https://ipmagnetize.example.com/"
A plain
docker composecommand loads this file automatically. If you list compose files yourself with-forCOMPOSE_FILE, include it in the list. -
.envholds the variables Compose itself reads (the ones marked.envabove), oneNAME=valueper line. Compose reads it from the project directory.
docker run -d -p 80:80 \
-e TRACKER_URL="https://ipmagnetize.example.com/" \
-v ipmagnetize-data:/var/www/data \
ghcr.io/nkboedecker/ipmagnetize:1The settings marked "override file" above become -e options. docker-compose.yml also sets a memory
cap, a process limit, no-new-privileges and log rotation; add the matching docker run options if you
want them. To run your own build, docker build -t ipmagnetize . in a checkout, and use ipmagnetize
as the image name.
docker-compose.swag.yml adds SWAG (nginx with automatic Let's Encrypt certificates) in front of
ipMagnetize on ports 80 and 443, and sets TRUST_PROXY=true on ipMagnetize. Certificates are issued
through Cloudflare's DNS-01 challenge, so the host doesn't need to be reachable during issuance.
Already running SWAG? Skip the overlay. Copy one of the templates from swag-templates/ into your
existing SWAG's nginx/proxy-confs/, put ipMagnetize on the same Docker network as SWAG so the hostname
ipmagnetize resolves, and set TRUST_PROXY=true.
- Your domain's DNS is hosted on Cloudflare.
- An
Arecord (plusAAAAif the host has IPv6) foripmagnetize.<your domain>pointing at the host, set to DNS only. If Cloudflare proxies the record, every hit is logged with a Cloudflare address. - A Cloudflare API token with only Zone:DNS:Edit and Zone:Zone:Read, scoped to that one zone.
-
Save the token where SWAG will look for it:
mkdir -p swag-config/dns-conf echo "dns_cloudflare_api_token = <token>" > swag-config/dns-conf/cloudflare.ini chmod 600 swag-config/dns-conf/cloudflare.ini
-
Create
.env:SWAG_URL=example.com SWAG_EMAIL=you@example.com IPMAGNETIZE_BIND=127.0.0.1 IPMAGNETIZE_PORT=8081 COMPOSE_FILE=docker-compose.yml:docker-compose.override.yml:docker-compose.swag.yml
IPMAGNETIZE_BIND=127.0.0.1keeps ipMagnetize's own port off the public interface, so only SWAG is reachable. The overlay can't do this itself: Compose adds upportsentries across files instead of replacing them.COMPOSE_FILEmakes plaindocker composecommands use all three files. The commands below assume it's set. On Windows, separate the files with;instead of:.
-
Create
docker-compose.override.ymlwith an https tracker URL:services: ipmagnetize: environment: TRACKER_URL: "https://ipmagnetize.example.com/"
SWAG redirects all plain HTTP to HTTPS, and not every BitTorrent client follows redirects when announcing, so an
http://tracker URL can lose hits. -
Start everything, then watch SWAG obtain its certificate. Wait for
Server ready:docker compose up -d docker compose logs -f swag
-
Install the proxy config and reload nginx.
swag-config/nginx/only exists after SWAG's first start, which is why this comes last:cp swag-templates/ipmagnetize.subdomain.conf.sample swag-config/nginx/proxy-confs/ipmagnetize.subdomain.conf docker compose exec swag nginx -s reload
Open https://ipmagnetize.<your domain>/. The page should show your public IP as "the address you've
accessed this page with". A 172.x.x.x address means TRUST_PROXY isn't in effect.
Pick one. TRACKER_URL can only point at one of them.
- Subdomain (
ipmagnetize.subdomain.conf.sample) serveshttps://ipmagnetize.<your domain>/. The overlay is set up for this. The subdomain name is fixed toipmagnetize. To change it, editSUBDOMAINSindocker-compose.swag.ymlandserver_namein the proxy config. - Subfolder (
ipmagnetize.subfolder.conf.sample) serveshttps://<your domain>/ipmagnetize/from SWAG's default site. The certificate then has to cover your bare domain: setONLY_SUBDOMAINS=falseindocker-compose.swag.yml, and point the bare domain's DNS at this host. That moves anything currently served there. SetTRACKER_URLtohttps://<your domain>/ipmagnetize/.
With a subdomain, SWAG's default site still answers requests for the host's bare IP, or any other
hostname, with a placeholder page. To close those connections without a reply, add return 444;
inside the listen 443 ssl default_server block of swag-config/nginx/site-confs/default.conf and
reload nginx. Skip this if you use the subfolder setup, or anything else on this SWAG uses subfolder
configs: they're served from that default site.
Set TRUST_PROXY=true only if ipMagnetize can be reached solely through your proxy. The proxy must
set X-Real-IP to the client's address and overwrite any value the client sent. In nginx:
proxy_set_header X-Real-IP $remote_addr;.
ipMagnetize uses the header only when all of these hold:
TRUST_PROXYistrue.- The connection comes from a private or loopback address.
- The header contains a valid IP address.
Otherwise it logs the connecting address. X-Forwarded-For is ignored, because clients can prepend
fake entries to it. That differs from upstream's reverse-proxy advice, which doesn't apply to this fork.
Host firewalls such as UFW and firewalld don't filter ports that Docker publishes. Docker inserts its
own iptables rules ahead of theirs, so ufw status can list only SSH while ipMagnetize's port 80, or
SWAG's 80 and 443, are open to the internet. To restrict them, either:
- Use a firewall in front of the host, such as a DigitalOcean Cloud Firewall or an AWS security group. Docker can't bypass it.
- Add rules to Docker's
DOCKER-USERiptables chain, which Docker checks before its own rules.
With the SWAG setup, IPMAGNETIZE_BIND=127.0.0.1 keeps ipMagnetize's own port off the network
whatever the firewall rules are.
git pull
docker compose pull
docker compose up -ddocker compose up -d alone keeps running the image you already have. The published image is rebuilt
every week on the newest php:8.4-apache, which is how PHP and Debian security fixes reach the
container, so run these regularly even when there's nothing to pull from git. Your .env and override
file aren't touched by git pull.
If you build from your checkout, run docker compose build --pull in place of docker compose pull:
the app's code is built into the image, and --pull fetches the newest base image. Your build takes
the published image's name, so a later docker compose pull replaces it with the published one.
SWAG is pinned to a tested release, so it only changes when this repository moves the pin (picked up
by the commands above) or when you set SWAG_VERSION in .env to a tag from
SWAG's releases and run docker compose up -d.
ghcr.io/nkboedecker/ipmagnetize has these tags. Set IPMAGNETIZE_VERSION in .env to choose one.
| Tag | What it is |
|---|---|
1.2.3 |
That release. |
1.2 |
The newest 1.2.x release. |
1 |
The newest 1.x release. docker-compose.yml uses this one unless you choose another. |
latest |
The newest release. |
edge |
The current master branch, which may not be released yet. |
Every week the newest release is rebuilt on the newest php:8.4-apache, and its tags are moved to the
rebuild, so the image behind a tag changes even without a new release. Only the newest release is
rebuilt: once 1.3.0 is out, 1.2 stops getting security fixes.
From master, tag the release and push the tag:
git tag -a v1.2.3 -m "ipMagnetize 1.2.3"
git push origin v1.2.3The Docker image workflow builds the tag for both platforms, runs test/smoke-test.sh against it,
and publishes it, then creates the release page on GitHub with the tag's message as its notes, so write
the message for readers. Before a new major version, change the IPMAGNETIZE_VERSION default in
docker-compose.yml to the new major version in the commit you tag. To test an image locally, build
it and run sh test/smoke-test.sh <image>.
GitHub makes a new package private. After the first release, make it public: open the package from the repository's Packages list, then Package settings, and Change visibility under Danger Zone. GitHub also pauses scheduled workflows in a repository with no activity for 60 days, which stops the weekly rebuilds. Its Actions tab offers to turn the workflow back on.
Hits are stored in SQLite at /var/www/data/ipmagnetize.db3, in the ipmagnetize-data volume. To
copy it out:
docker compose cp ipmagnetize:/var/www/data/ipmagnetize.db3 ./ipmagnetize-backup.db3You can bind-mount a host directory at /var/www/data instead. The container fixes its ownership at
startup. For users' privacy, upstream recommends wiping the database regularly.
Until October 2026 this fork used ipmagnet for the Compose service, its volume, the IPMAGNET_*
settings and SWAG's subdomain. After pulling the rename, an existing install starts with an empty
database unless you move it across:
-
In
.env, renameIPMAGNET_BIND,IPMAGNET_PORTandIPMAGNET_MEM_LIMITtoIPMAGNETIZE_BIND,IPMAGNETIZE_PORTandIPMAGNETIZE_MEM_LIMIT. Indocker-compose.override.yml, rename theipmagnet:service toipmagnetize:and pointTRACKER_URLat the new hostname. -
Remove the old containers (
--remove-orphanscatches the oldipmagnetone, which the compose files no longer name), create the new ones without starting them, and copy the database:docker compose down --remove-orphans docker compose up --no-start --build docker run --rm -v <project>_ipmagnet-data:/from:ro -v <project>_ipmagnetize-data:/to \ alpine cp /from/ipmagnet.db3 /to/ipmagnetize.db3
<project>is the Compose project name, by default the checkout's directory name.docker volume lslists both volumes. -
With SWAG, swap the proxy config, then start everything:
rm swag-config/nginx/proxy-confs/ipmagnet.subdomain.conf cp swag-templates/ipmagnetize.subdomain.conf.sample swag-config/nginx/proxy-confs/ipmagnetize.subdomain.conf docker compose up -d
SWAG requests a certificate for
ipmagnetize.<your domain>as it starts, so create that DNS record first. Magnet links handed out before the move still announce to the old hostname.
The old volume stays until you remove it with docker volume rm.