Files
DiscordBot-CloudflareWarp/README.md
T
ginoblij 1a9dc1d676 Initial Discord bot in Docker behind a per-container Cloudflare WARP tunnel
- discord.js 14 bot with /ping and /warp, periodic WARP egress-IP logging
- Dockerfile: node:22-bookworm-slim + cloudflare-warp 2026.7.1377.0 (.deb)
- entrypoint boots dbus/warp-svc, registers, connects, verifies warp=on
- docker-compose with NET_ADMIN, /dev/net/tun, sysctls, healthcheck
- deploy script, README, CHANGELOG, lockfile generated inside Docker

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 01:16:32 +02:00

6.2 KiB

DiscordBot-CloudflareWarp

A small discord.js bot that runs entirely inside Docker and sends all of its traffic through a Cloudflare WARP (Cloudflare One / Zero Trust client) tunnel. Every container runs its own WARP client, so every bot gets its own VPN tunnel and its own Cloudflare egress IP. The bot proves it is still on Cloudflare by logging the egress IP and warp=on flag on startup, on a timer and on demand.

Nothing runs on the host except Docker: npm install happens in the image build, and the lockfile was generated inside a node:22-bookworm-slim container.

How it works

+-------------------------------------------------------------+
|  container: discordbot-warp                                 |
|                                                             |
|  dbus  ->  warp-svc (Cloudflare WARP daemon, TUN device)    |
|                 ^                                           |
|  entrypoint.sh: register -> mode warp -> connect -> verify  |
|                 |                                           |
|  node src/index.js  ---- all traffic ----> WARP tunnel ---> Cloudflare ---> Discord
+-------------------------------------------------------------+
  1. docker/entrypoint.sh starts dbus and warp-svc, creates (or reuses) a WARP registration, sets mode warp, connects and waits until warp-cli status says Connected.

  2. It then calls https://www.cloudflare.com/cdn-cgi/trace and refuses to start the bot unless Cloudflare reports warp=on (or warp=plus). The egress IP and colo are logged.

  3. bot/src/index.js logs into Discord and repeats the same trace check every WARP_CHECK_INTERVAL seconds (default 60), logging a line like:

    [2026-09-08T00:20:15.123Z] [WARP] CONNECTED via Cloudflare (periodic) ip=104.28.x.x warp=on colo=AMS loc=NL http=http/2 tls=TLSv1.3 | warp-cli: Status update: Connected
    

    If the tunnel drops it logs NOT CONNECTED and runs warp-cli connect to recover.

  4. A Docker HEALTHCHECK performs the same warp=on check, so docker ps shows unhealthy if the container ever falls off Cloudflare.

Slash commands

Command What it does
/ping Replies with round-trip and gateway latency.
/warp Runs a live tunnel check and replies with the Cloudflare IP, colo and status.

Repository layout

bot/
  package.json, package-lock.json   Node project (discord.js 14)
  src/index.js                      the bot
docker/
  Dockerfile                        node:22-bookworm-slim + cloudflare-warp .deb
  entrypoint.sh                     boots WARP, verifies, then execs the bot
docker-compose.yml                  one service per bot, each with its own WARP tunnel
scripts/deploy.sh                   ships the committed tree to the Docker host and restarts the stack
.env.example                        configuration template
CHANGELOG.md                        every change, newest first

Requirements

  • A Docker host (tested on Debian 12 with Docker 20.10 and Compose v5) with /dev/net/tun available.
  • Containers need NET_ADMIN and the TUN device. Both are set in docker-compose.yml.
  • A Discord bot token from the Developer Portal.

The Cloudflare WARP client is downloaded during the image build from https://downloads.cloudflareclient.com/v1/download/bookworm-intel/version/<WARP_VERSION> (WARP_VERSION defaults to 2026.7.1377.0, the Debian 12 build).

Quick start (on the Docker host)

git clone https://gitea.ikbengino.nl/ginoblij/DiscordBot-CloudflareWarp.git
cd DiscordBot-CloudflareWarp
cp .env.example .env          # put your DISCORD_TOKEN in .env
docker compose up -d --build
docker compose logs -f

You should see the entrypoint report the tunnel, then the bot log in:

[entrypoint] warp-svc is up
[entrypoint] no registration found, creating a new one
[entrypoint] mode set to warp
[entrypoint] warp-cli status: Status update: Connected
[entrypoint] WARP tunnel verified: ip=104.28.x.x warp=on colo=AMS
[entrypoint] starting bot: node src/index.js
[bot] logged in as ... in 1 guild(s)
[WARP] CONNECTED via Cloudflare (startup) ip=104.28.x.x warp=on colo=AMS ...

Configuration (.env)

Variable Default Meaning
DISCORD_TOKEN Bot token. Required.
WARP_CHECK_INTERVAL 60 Seconds between tunnel checks logged by the bot.
GUILD_ID Register slash commands in one guild (instant) instead of globally.
WARP_MODE warp warp-cli mode to use (warp, doh, warp+doh, ...).
WARP_CONNECT_TIMEOUT 60 Seconds the entrypoint waits for Connected before giving up.

Running more than one bot (one VPN per container)

Each service in docker-compose.yml is an independent container with its own warp-svc, its own registration volume and therefore its own tunnel and egress IP. To add a bot:

  1. Create .env.bot2 with the second token.
  2. Uncomment the bot2 service and the warp-data-bot2 volume in docker-compose.yml.
  3. docker compose up -d --build.

Deploying from this repo

scripts/deploy.sh sends the committed tree (git archive HEAD) to the host over SSH, rebuilds the image there and restarts the stack. The .env on the host is never touched or overwritten.

scripts/deploy.sh                          # defaults to root@192.168.5.67:/opt/discordbot-cloudflarewarp
scripts/deploy.sh user@host /some/dir

Checking the tunnel by hand

docker compose exec bot warp-cli --accept-tos status
docker compose exec bot curl -s https://www.cloudflare.com/cdn-cgi/trace
docker compose logs -f | grep WARP

Change history

See CHANGELOG.md. Every change is committed and pushed with a changelog entry.