feat: cloudflared container with optional WireGuard egress

- Dockerfile.cloudflared builds cloudflared (official static binary) on Alpine with wireguard-tools
This commit is contained in:
dev
2026-09-12 17:40:42 +03:00
parent 9203eee5d2
commit dd4d306a4e
5 changed files with 66 additions and 9 deletions
+1
View File
@@ -6,6 +6,7 @@
*.key *.key
*.crt *.crt
certs/ certs/
wg/
# --- Run-time data / uploads --- # --- Run-time data / uploads ---
uploads/ uploads/
+12
View File
@@ -0,0 +1,12 @@
# Сборка контейнера cloudflared с опциональным WireGuard.
# Официальный образ cloudflare/cloudflared — distroless (без оболочки/apk), поэтому:
# 1) берём из него статичный бинарник cloudflared;
# 2) саму сборку делаем на Alpine, где ставим wireguard-tools.
# Если в wg/wg0.conf нет конфига — VPN не поднимается, туннель запускается как обычно.
FROM cloudflare/cloudflared:latest AS cf
FROM alpine:3.20
RUN apk add --no-cache wireguard-tools iproute2
COPY --from=cf /usr/local/bin/cloudflared /usr/bin/cloudflared
COPY start-cloudflared.sh /start-cloudflared.sh
ENTRYPOINT ["/bin/sh", "/start-cloudflared.sh"]
+20 -1
View File
@@ -182,7 +182,26 @@ docker exec whatido-tailscale-1 tailscale funnel status
docker compose logs cloudflared | grep trycloud docker compose logs cloudflared | grep trycloud
``` ```
Адрес меняется при каждом перезапуске контейнера `cloudflared`. Для постоянного адреса используйте именованный туннель (замените `command` сервиса `cloudflared` в `docker-compose.yml` на `["tunnel", "run", "--token", "${CLOUDFLARE_TUNNEL_TOKEN}"]`, токен — из Cloudflare Zero Trust **Networks → Tunnels**) или схему через Tailscale выше. ### Опциональный WireGuard для эгресса Cloudflare
`cloudflared` собирается из `Dockerfile.cloudflared`, в который добавлена поддержка **опционального WireGuard** — полезно, когда Cloudflare не работает с прямого IP хоста, и исход туннеля нужно пустить через VPN.
- Положите рабочий конфиг провайдера в `wg/wg0.conf` (папка `wg/` монтируется в контейнер как `/etc/wireguard`). Пример-заглушка уже лежит в `wg/wg0.conf` — замените значения на свои.
- Если `wg/wg0.conf` есть → контейнер **сначала поднимает WireGuard, дожидается handshake и только потом запускает туннель** (весь эгресс Cloudflare идёт через VPN).
- Если `wg0.conf` нет или папка пустая → VPN не включается, туннель стартует сразу, как в базовой схеме.
Проверка:
```bash
docker compose logs -f cloudflared # в идеале сначала "WireGuard is up", затем URL
docker compose exec cloudflared wg show # есть handshake — VPN поднят
```
Примечания:
- `wg/wg0.conf` содержит приватный ключ, поэтому папка `wg/` добавлена в `.gitignore` (в репозиторий не попадёт).
- При полном туннеле (`AllowedIPs = 0.0.0.0/0`) существующий маршрут docker-сети сохраняется, поэтому `cloudflared` по-прежнему достаёт `app` локально, а наружу уходит через wg0. Если вдруг `app` станет недоступен из-за VPN — переключите `CLOUDFLARE_TUNNEL_URL` в `.env` на docker-шлюз (`http://<gateway-ip>:3003`).
Адрес Quick Tunnel меняется при каждом перезапуске контейнера `cloudflared`. Для постоянного адреса используйте именованный туннель (для этого в `start-cloudflared.sh` замените последнюю команду на `exec cloudflared --no-autoupdate tunnel run --token "$CLOUDFLARE_TUNNEL_TOKEN"` и задайте токен; токен — из Cloudflare Zero Trust **Networks → Tunnels**) или схему через Tailscale выше.
## Структура данных ## Структура данных
+13 -8
View File
@@ -68,22 +68,27 @@ services:
# Публикация наружу через Cloudflare Tunnel (cloudflared), Quick Tunnel: # Публикация наружу через Cloudflare Tunnel (cloudflared), Quick Tunnel:
# случайный публичный URL *.trycloudflare.com, который печатается в логах: # случайный публичный URL *.trycloudflare.com, который печатается в логах:
# docker compose logs -f cloudflared # docker compose logs -f cloudflared
# ВАЖНО: официальный образ cloudflare/cloudflared НЕ содержит /bin/sh, # Сборка из Dockerfile.cloudflared добавляет опциональный WireGuard:
# поэтому логика передаётся аргументами в command (entrypoint образа = cloudflared), # если в wg/wg0.conf лежит конфиг — контейнер сначала поднимает VPN и
# а не скриптом. # только потом запускает туннель (исход Cloudflare через VPN). Без конфига
# Для именованного туннеля на свой домен замените command ниже на: # туннель стартует сразу, как в базовой схеме.
# command: ["tunnel", "run", "--token", "${CLOUDFLARE_TUNNEL_TOKEN}"]
# (токен получается в Cloudflare Zero Trust -> Networks -> Tunnels).
cloudflared: cloudflared:
image: cloudflare/cloudflared:latest build:
context: .
dockerfile: Dockerfile.cloudflared
restart: unless-stopped restart: unless-stopped
cap_add:
- NET_ADMIN
volumes:
- ./wg:/etc/wireguard:ro
environment: environment:
TZ: Europe/Moscow TZ: Europe/Moscow
command: ["tunnel", "--url", "${CLOUDFLARE_TUNNEL_URL:-http://app:3003}"] CLOUDFLARE_TUNNEL_URL: ${CLOUDFLARE_TUNNEL_URL:-http://app:3003}
depends_on: depends_on:
- app - app
text-corrector: text-corrector:
image: ghcr.io/ggml-org/llama.cpp:server image: ghcr.io/ggml-org/llama.cpp:server
container_name: text-corrector container_name: text-corrector
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
set -e
# Опциональный WireGuard: если в wg/wg0.conf лежит конфиг — поднимаем VPN
# и только после установки handshake запускаем туннель. Без конфига —
# cloudflared стартует сразу, как в базовой схеме Quick Tunnel.
if [ -f /etc/wireguard/wg0.conf ]; then
echo "WireGuard config found, bringing up wg0..."
wg-quick up wg0
echo "Waiting for WireGuard handshake..."
# Формат `wg show wg0 latest-handshakes`: "<pubkey> <epoch-ts>"; 0 = handshake ещё не было.
until wg show wg0 latest-handshakes | awk '{ if ($2 != 0) found=1 } END { exit !found }'; do
sleep 2
done
echo "WireGuard is up, starting Cloudflare tunnel through VPN..."
else
echo "No WireGuard config, starting Cloudflare tunnel directly..."
fi
exec cloudflared --no-autoupdate tunnel --url "$CLOUDFLARE_TUNNEL_URL"