# DiscordBot-CloudflareWarp A small [discord.js](https://discord.js.org/) 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](https://discord.com/developers/applications). The Cloudflare WARP client is downloaded during the image build from `https://downloads.cloudflareclient.com/v1/download/bookworm-intel/version/` (`WARP_VERSION` defaults to `2026.7.1377.0`, the Debian 12 build). ## Quick start (on the Docker host) ```bash 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. ```bash 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 ```bash 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](CHANGELOG.md). Every change is committed and pushed with a changelog entry.