Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
ARG DEBIAN_RELEASE=bullseye
FROM docker.io/debian:$DEBIAN_RELEASE-slim
FROM docker.io/debian:${DEBIAN_RELEASE}-slim

ARG DEBIAN_RELEASE
ENV DEBIAN_FRONTEND=noninteractive

RUN true && \
apt update && \
apt install -y gnupg ca-certificates curl socat
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gnupg \
ca-certificates \
curl \
gzip \
tar \
jq && \
rm -rf /var/lib/apt/lists/*

RUN curl https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg && \
ARCH="$(dpkg --print-architecture)" && \
echo "deb [arch=${ARCH} signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $DEBIAN_RELEASE main" | tee /etc/apt/sources.list.d/cloudflare-client.list && \
apt update && \
apt install cloudflare-warp -y --no-install-recommends
RUN ARCH="$(dpkg --print-architecture)" && \
case "${ARCH}" in \
amd64) GOST_ARCH="amd64" ;; \
arm64) GOST_ARCH="arm64" ;; \
armhf) GOST_ARCH="armv7" ;; \
*) echo "Unsupported architecture" && exit 1 ;; \
esac && \
DOWNLOAD_URL=$(curl -fsSL https://api.github.com/repos/go-gost/gost/releases/latest | \
jq -r --arg arch "linux_${GOST_ARCH}.tar.gz" '.assets[] | select(.name | endswith($arch)) | .browser_download_url' | head -n 1) && \
if [ -z "$DOWNLOAD_URL" ]; then echo "Error: Failed to find download URL for architecture ${GOST_ARCH}"; exit 1; fi && \
echo "Downloading GOST from: $DOWNLOAD_URL" && \
curl -fsSL "$DOWNLOAD_URL" | tar -C /usr/local/bin -xzf - gost && \
chmod +x /usr/local/bin/gost

COPY entrypoint.sh /
RUN curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | \
gpg --dearmor -o /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg && \
ARCH="$(dpkg --print-architecture)" && \
echo "deb [arch=${ARCH} signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${DEBIAN_RELEASE} main" \
> /etc/apt/sources.list.d/cloudflare-client.list

RUN apt-get update && \
apt-get install -y --no-install-recommends cloudflare-warp && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

RUN apt remove -y curl && \
apt clean -y && \
rm -rf /var/lib/apt/lists/* && \
chmod +x /entrypoint.sh
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh

EXPOSE 40000/tcp
ENTRYPOINT [ "/entrypoint.sh" ]

HEALTHCHECK --interval=15s --timeout=10s --start-period=30s --retries=2 \
CMD curl -fsSL https://www.cloudflare.com/cdn-cgi/trace \
-x socks5h://127.0.0.1:40000 >/dev/null || exit 1

ENTRYPOINT ["/entrypoint.sh"]
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ Docker image to run Cloudflare Warp in proxy mode. Image is rebuilt and updated
## Usage

### docker hub image
```
```bash
docker run -d -p 40000:40000 --restart unless-stopped seiry/cloudflare-warp-proxy

```

### or github package image
```

```bash
docker run -d -p 40000:40000 --restart unless-stopped ghcr.io/seiry/cloudflare-warp-proxy

```

A **SOCKS5** proxy will be listening at port 40000 (this is WARP's proxy mode,
exposed via `socat`). The same port also accepts HTTP `CONNECT`, so you can point
either a `socks5h://` / `socks5://` or an `http://` client at it — see [test](#test).
A **multiplexed** proxy will be listening at port 40000. Under the hood, this uses `gost` to forward traffic to WARP's internal proxy. This setup allows the same port to automatically accept **both SOCKS5 and HTTP** proxy connections seamlessly. You can point a `socks5h://`, `socks5://`, or an `http://` client directly at it — see [test](#test).

### docker-compose

Expand Down Expand Up @@ -100,14 +101,15 @@ using a **service token**. The same file pins the partner's endpoint overrides

`service_mode`/`proxy_port` put the client into proxy mode locally. The Cloudflare One
Client gives precedence to local settings, so this overrides the org's device profile —
keep `proxy_port` at `40001` to match the `socat` forward (`40000 → 40001`) in the container.
keep `proxy_port` at `40001` to match the `gost` forward (`40000 → 40001`) in the container.

### 3. run with the file mounted

```
```bash
docker run -d -p 40000:40000 --restart unless-stopped \
-v ./mdm.xml:/var/lib/cloudflare-warp/mdm.xml:ro \
seiry/cloudflare-warp-proxy

```

A service-token enrolled device shows up under **My Team → Devices** with email
Expand Down Expand Up @@ -140,7 +142,7 @@ curl https://www.cloudflare.com/cdn-cgi/trace -x http://127.1:40000 # http mode
...
sni=plaintext
warp=on
# 👆wrap on!
# 👆warp on!
gateway=off
...
```
Expand Down
37 changes: 31 additions & 6 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ else
# Consumer deployment: create a free registration. Newer clients auto-register on
# startup and the registration persists across restarts, so "registration new" can
# fail with "Old registration is still around" — treat that as success instead of
# looping forever (otherwise socat below never starts).
# looping forever (otherwise gost below never starts).
while true; do
if out="$(warp-cli --accept-tos registration new 2>&1)"; then
break
Expand All @@ -31,19 +31,44 @@ fi
# the mode is dictated by the org device profile and these calls return "Invalid setting
# for this account type" — in that case set the profile's service mode to "Proxy mode"
# (port 40001) in the Zero Trust dashboard, and ignore the errors below.
warp-cli --accept-tos mode proxy 2>/dev/null || >&2 echo "mode set by org profile, skipping local mode/port"
warp-cli --accept-tos mode proxy 2>/dev/null || true
warp-cli --accept-tos proxy port 40001 2>/dev/null || true

# docker-compose's `- LICENSE='...'` keeps the surrounding quotes as part of the value,
# which makes warp-cli reject the key — strip a single pair of leading/trailing quotes.
LICENSE="${LICENSE#[\"\']}"
LICENSE="${LICENSE%[\"\']}"

if [ "$LICENSE" != "" ]; then
warp-cli --accept-tos registration license "$LICENSE" || true
warp-cli --accept-tos registration license "$LICENSE" >/dev/null 2>&1 || true
fi

warp-cli --accept-tos connect
socat TCP-LISTEN:40000,fork TCP:localhost:40001 # socat is used to redirect traffic from 40000 to 40001
# Connect WARP silently
warp-cli --accept-tos connect >/dev/null 2>&1


# ==========================================
# GOST MEMORY Limit
# ==========================================
export GOMEMLIMIT=50MiB
export GOGC=30

# Gost acts as a multiplexed proxy listening on 40000. Using the 'auto://' scheme,
# it automatically detects and supports incoming traffic for:
# - HTTP proxy
# - HTTPS proxy (CONNECT)
# - SOCKS4
# - SOCKS5
# and forwards that traffic directly to Cloudflare Warp's SOCKS5 on 40001.
gost -C '{"log": {"level": "warn"}}' -L "auto://:40000" -F "socks5://127.0.0.1:40001" &
) &

exec warp-svc
# ==========================================
# WARP-SVC WITH LOG FILTER
# ==========================================
warp-svc 2>&1 | grep --line-buffered -vE 'Initializing dbus connection|org\.freedesktop\.DBus\.Error\.FileNotFound' &
WARP_PID=$!

# Ensure graceful shutdown if container is stopped
trap 'kill -TERM $WARP_PID 2>/dev/null' TERM INT
wait $WARP_PID