Proxy that gives outbound requests a real browser network profile: TLS/JA3, HTTP/2, HTTP/3, headers
688
An HTTP/SOCKS5 proxy that gives outbound requests the network profile of a real browser. TLS (JA3/JA4), HTTP/2 settings, HTTP/3, header order and connection parameters are shaped as one consistent configuration — not a user-agent string glued onto a Node.js or Python client.
Next to n8n this means the HTTP Request node goes out looking like a browser rather than like its runtime.
docker run -d --name blanktrail \
-e BT_API_KEY=your-control-api-key-16-chars-min \
-e BT_ALLOW_LAN=1 \
-e BT_PROXY_USER=n8n \
-e BT_PROXY_PASSWORD=your-proxy-password \
-v bt-data:/data -v bt-ca:/ca \
--cap-add NET_ADMIN --device /dev/net/tun \
-p 127.0.0.1:10001:10001 \
-p 127.0.0.1:8891:8891 \
-p 127.0.0.1:8892:8892 \
blanktrail/blanktrail-proxy:1.3.11
The proxy is then at http://127.0.0.1:10001, the control API at
http://127.0.0.1:8891, and the anti-captcha.com-compatible API — once you
switch it on — at http://127.0.0.1:8892.
A fresh container comes up unlicensed, and an unlicensed client opens no proxy
port: port 10001 appears once the licence is in place. Sign in with your
Cabinet account to put it there — in the dashboard on 127.0.0.1:8891, or, if
you would rather not publish the dashboard at all, with a single call:
curl -X POST http://127.0.0.1:8891/api/v1/license/enroll \
-H "X-API-Key: $BT_API_KEY" -H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"your-cabinet-password"}'
It is a one-off: the licence lands in the /data volume and survives restarts.
There is deliberately no environment variable for the account password — an
environment variable is readable in docker inspect, kept in the shell history
and usually committed next to a compose file, which is too much exposure for the
password to your account. An already-issued licence pair is a different matter,
and BT_LICENSE_KEY/BT_LICENSE_SECRET still take one (see below).
Only exact release versions are published — 1.3.11 today. There is deliberately
no moving 1.3 tag: what the registry shows is exactly what was released, and
moving to a new version stays a decision you make in your compose file rather
than something that arrives on the next pull.
Every tag is multi-arch: linux/amd64 and linux/arm64.
| Variable | Meaning |
|---|---|
BT_API_KEY | Control API key. Leave it empty and the product generates one, printing it on first start as initial API key generated (warn level, so it does not get lost in the log) and writing it to /data/initial-credentials.txt. Set it yourself only if you already have a key: 16–128 printable ASCII characters, no spaces. Either way it is seeded only when the /data volume is first created — editing it later changes nothing, because the key lives in /data/auth.json. |
BT_LICENSE_KEY, BT_LICENSE_SECRET | An already-issued licence pair — one installation's machine credential, not your account password. Seeded only while /data holds no licence yet. Leave both empty and activate from the dashboard instead. |
BT_PROXY_USER | Proxy username, printable ASCII without spaces, at most 128 characters. Optional: leave it empty and it becomes blanktrail as soon as a password is set, because requiring two variables where one carries the meaning only lengthens the very first step. Seeded only when the /data volume is first created — it lives in /data/auth.json afterwards. |
BT_PROXY_PASSWORD | Proxy password, 8–128 characters; leading and trailing spaces are preserved, a space is a legal password character. Mandatory in practice. The product refuses to start when BT_ALLOW_LAN is set and this is empty — an unauthenticated proxy bound to every interface is open to everything that can reach the container. And clearing BT_ALLOW_LAN to dodge that leaves a port nothing can reach, so there is no third option. Seeded only when the /data volume is first created; change it later in the dashboard, under Settings → Proxy authentication, not by editing this variable. |
BT_ALLOW_LAN | Bind the proxy ports on every interface inside the container rather than on its loopback. Set in both compose files shipped with the image, and needed by anything that has to reach the proxy: a neighbouring container gets connection refused without it, and so does a published port, because Docker forwards to the container address rather than to its loopback. It says nothing about your host — what is exposed there is decided by ports in compose or -p in docker run. Requires BT_PROXY_PASSWORD. |
BT_CAPTCHA_API_ENABLED | Start the anti-captcha.com-compatible listener on port 8892. Off by default. You can also switch it on in the dashboard (Settings → Network) — that takes effect immediately, with no container restart. |
BT_CAPTCHA_API_ADDR | A different address or port for that listener. Defaults to :8892. |
BT_CAPTCHA_API_ALLOW_LAN | Already on in this image's configuration, so you normally do not set it. It matters only if you mount a configuration of your own: inside a container a request arrives from the Docker bridge address rather than from the loopback, and with allow_lan off the bare host :8892 binds to 127.0.0.1 — the published port then answers connection refused. BT_ALLOW_LAN does not cover this listener; it is about the proxy ports and the dashboard. |
BT_INTEGRATION_KEY | Software-developer integration key, if you have one. |
docker compose logs blanktrail | grep "initial API key"
The log line advises opening the dashboard. That is right for a desktop install;
in this image the dashboard is not published to the network, so read the key from
the log or from /data/initial-credentials.txt.
BT_ALLOW_LAN without BT_PROXY_PASSWORD refuses to start. An
unauthenticated proxy bound to every interface is open to everything that can
reach the container, so the product stops instead of quietly serving it. The
message names the variable, never its value — container logs are what people
paste into support.
In practice that makes the password mandatory. A proxy port that nothing can
reach is of no use, and reaching it needs BT_ALLOW_LAN: without it the port
binds 127.0.0.1 inside the container, where the only client is the container
itself — a neighbouring n8n gets connection refused, and so does a published
port, because Docker forwards to the container address rather than to its
loopback. Both compose files shipped with the image therefore set
BT_ALLOW_LAN: "1" outright, and both refuse to come up until you fill
BT_PROXY_PASSWORD in.
Two consequences worth separating:
BT_ALLOW_LAN is about the binding inside the container, not about your
host. What the outside world sees is decided by ports in compose, or -p
in docker run. The compose files publish no proxy port at all: n8n reaches
it over the compose network by service name.BT_PROXY_PASSWORD and the username
becomes blanktrail. Requiring two variables where one carries the meaning
only lengthens the very first step.Both are seeded only when the /data volume is first created, exactly like
BT_API_KEY: the pair lives in /data/auth.json afterwards. Editing the
variable on a volume that already exists changes nothing — otherwise a password
you changed in the dashboard would come back on every restart. Change it in the
dashboard, under Settings → Proxy authentication, or delete the volume and its
whole state with it.
/data | Licence, device identity, profile database, logs. Back this up — losing it means re-enrolling the device. |
/ca | The MITM certificate authority. Mount it read-only into the client container and point it there. |
8891 | Control API and dashboard. Keep it on loopback; it is not meant to face a network. |
8892 | The anti-captcha.com-compatible API, off by default. Published on loopback in the compose files shipped with the image — publish it wider only if the tool that solves captchas runs on another machine. It authenticates with the same API key as the dashboard. |
10001 | The proxy itself. |
A healthcheck is built in. Its start period is 90 seconds: the first run sets the device up and, on plans that include it, downloads a large solver bundle. It reports the control API, not the licence — a container waiting to be activated is healthy and serves no proxy port.
services:
blanktrail:
image: blanktrail/blanktrail-proxy:1.3.11
environment:
BT_API_KEY: ${BT_API_KEY}
BT_PROXY_USER: ${BT_PROXY_USER}
BT_PROXY_PASSWORD: ${BT_PROXY_PASSWORD}
BT_ALLOW_LAN: "1"
volumes:
- bt-data:/data
- bt-ca:/ca
ports:
# The captcha API, on loopback. Published even while it is switched off:
# otherwise turning it on from the dashboard would give you a listener
# running inside the container that nothing outside can reach — which
# looks exactly like the switch not working.
- "127.0.0.1:8892:8892"
restart: unless-stopped
n8n:
image: docker.n8n.io/n8nio/n8n
environment:
NODE_EXTRA_CA_CERTS: /ca/ca.crt
volumes:
- bt-ca:/ca:ro
depends_on:
blanktrail:
condition: service_healthy
volumes:
bt-data:
bt-ca:
NODE_EXTRA_CA_CERTS is what makes n8n trust the proxy's certificate. Without
it every HTTPS request through the proxy fails certificate validation.
Opening n8n from another machine? n8n marks its session cookie Secure by
default and refuses to show its first-run setup page over plain HTTP on anything
but localhost — you get a "Your n8n server is configured to use a secure
cookie" placeholder, which has nothing to do with the proxy. Put TLS in front of
n8n, or tunnel to it (ssh -L 5678:127.0.0.1:5678 user@host), or set
N8N_SECURE_COOKIE: "false" if the box sits on a network you trust.
There is a community node that wires this up for you —
n8n-nodes-blanktrail: it
opens a port with the profile you pick, fetches the CA over the control API and
routes the request through it.
10001 is opened as HTTP. A port serves one protocol at a time —
open further ports through the control API for SOCKS5 or MTProto, each with
its own browser profile.10001 is opened only on a fresh volume. After that, the ports you have
open are what comes back on restart: switch 10001 to SOCKS5 or close it,
and it stays that way.xray (VLESS/Reality,
Trojan, Shadowsocks, VMess, Hysteria2 — no special rights) and openvpn.
OpenVPN gateways need NET_ADMIN and /dev/net/tun: both are set in the
shipped compose files, and the docker run above passes them. Without them
OpenVPN gateways refuse; everything else works. The tunnel lives in the
container's network namespace — with --network host it would land in the
host's own network stack instead.tini as PID 1, with the product as its child. Every browser
the solver closes leaves orphaned processes behind, and reaping orphans is
PID 1's job — so you need neither --init nor init: true (both are
harmless). If you override the entrypoint, keep an init with --init:
without one, each closed solver browser leaves about nine zombie processes
that pile up until the container restarts.Docs and account: blanktrail.com
Content type
Image
Digest
sha256:4ba5a3e8b…
Size
119.3 MB
Last updated
1 day ago
docker pull blanktrail/blanktrail-proxy:1.3.11