Создаем сеть:
docker network create vpn
Готовим образ с Wireguard:
FROM debian:trixie-slim@sha256:77ba0164de17b88dd0bf6cdc8f65569e6e5fa6cd256562998b62553134a00ef0
# wg-quick requirements: iproute2, procps
# envsubst util: gettext-base
RUN apt-get update &&
apt-get install -y wireguard iproute2 procps gettext-base wget &&
apt-get clean &&
rm -rf /var/lib/apt/lists/*
# Patch wg-quick if getting "sysctl: permission denied on key net.ipv4.conf.all.src_valid_mark" error.
#
# Credits: @kianbahasadri from (https://forums.docker.com/t/sysctl-error-setting-key-net-ipv4-conf-all-src-valid-mark-read-only-file-system/92567/10)
# and https://github.com/linuxserver/docker-wireguard
#
# See also
# https://github.com/wg-easy/wg-easy/issues/2261
# and
# https://github.com/WireGuard/wireguard-tools/pull/29/files
RUN sed -i 's|[[ $proto == -4 ]] && cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|[[ $proto == -4 ]] && [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] && cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' /usr/bin/wg-quick
RUN mkdir /opt/app
WORKDIR /opt/app
RUN wget -O - https://github.com/DNSCrypt/dnscrypt-proxy/releases/download/2.1.15/dnscrypt-proxy-linux_x86_64-2.1.15.tar.gz | tar -xz && cp linux-x86_64/dnscrypt-proxy ./ && rm -r linux-x86_64
COPY ./dnscrypt-proxy.toml ./
COPY ./wg0.conf-template ./
Можно взять linuxserver/wireguard и не накладывать патч на wg-quick из-за ограничений в контейнере
name: vpn
services:
wireguard:
image: wireguard
build: .
container_name: vpn
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
sysctls:
# Patch wg-quick if getting "sysctl: permission denied on key net.ipv4.conf.all.src_valid_mark" error.
#
# Credits: @kianbahasadri from (https://forums.docker.com/t/sysctl-error-setting-key-net-ipv4-conf-all-src-valid-mark-read-only-file-system/92567/10)
# and https://github.com/linuxserver/docker-wireguard
#
# See also
# https://github.com/wg-easy/wg-easy/issues/2261
# and
# https://github.com/WireGuard/wireguard-tools/pull/29/files
- net.ipv4.conf.all.src_valid_mark=1
networks:
- vpn
dns:
- 127.0.0.1
command: bash -c "envsubst < wg0.conf-template > /etc/wireguard/wg0.conf && wg-quick up wg0 && ./dnscrypt-proxy"
environment:
# required
- WG_ADDRESS=
- WG_PRIVATEKEY=
- WG_PEER_PUBLICKEY=
- WG_PEER_ENDPOINT=
# Network creation: `docker network create vpn`
networks:
vpn:
external: true
Задаем нужные значения для WG_* переменных в docker-compose.yml
wg0.conf-template:
[Interface]
Address = ${WG_ADDRESS}
ListenPort = 51820
PrivateKey = ${WG_PRIVATEKEY}
[Peer]
PublicKey = ${WG_PEER_PUBLICKEY}
Endpoint = ${WG_PEER_ENDPOINT}
AllowedIPs = 0.0.0.0/0
Значения переменных заполняются через envsubst.
Собираем и запускаем:
docker compose build --progress plain wireguard
docker compose up -d wireguard
Допустим, есть приложение в контейнере (app), с отдельным Docker Compose файлом, которое нужно пустить в сеть через Wireguard и оно зависит еще от одного приложения (redis):
services:
app:
image: debian:trixie-slim@sha256:77ba0164de17b88dd0bf6cdc8f65569e6e5fa6cd256562998b62553134a00ef0
network_mode: "container:vpn"
depends_on:
- redis
redis:
image: redis:8.4.0-bookworm@sha256:73dad4271642c5966db88db7a7585fae7cf10b685d1e48006f31e0294c29fdd7
networks:
- vpn
networks:
vpn:
external: true
Запускаем: docker compose up -d app
Проверяем, что приложение ходит по сети через тоннель: docker compose run --rm app bash -c "apt-get update && apt-get install -y curl && curl 'https://ident.me'"
Еще хорошо бы проверить (tcpdump + Wireshark), что Wireguard контейнер не шлет DNS запросы в обход Wireguard тоннеля.
Проверяем, что зависимость приложения (redis) доступна по имени сервиса: docker compose run --rm app bash -c "apt-get update && apt-get install -y redis-tools && redis-cli -h redis set a b"
Как это работает
network_mode: "container:vpn" помещает контейнер app в сеть контейнера wireguard. По документации не очень понятно, что кроме ID контейнера (который CONTAINER ID из docker ps) можно указывать и имя контейнера (NAMES из docker ps). Интересно, что docker inspect app-app-1 не показывает, что контейнер вообще состоит в какой-нибудь сети (пустые поля в "NetworkSettings").
container_name: vpn задает удобное короткое имя контейнера, чтобы не указывать wireguard-wireguard-1
Без добавления redis во внешнюю сеть vpn, в которой состоит и wireguard, app бы не смог найти redis по имени, ведь он видит по сети только то, что видит wireguard.
DNS запросы из app идут через wireguard, но без дополнительных настроек они бы шли далее через машину, где запущен контейнер wireguard. Один из способов это исправить - слать "DNS over HTTPS" запросы через тоннель. Для этого контейнер wireguard настроен (dns: - 127.0.0.1) на локальный DNSCrypt.
dnscrypt-proxy.toml:
server_names = ['static_cloudflare']
listen_addresses = ['127.0.0.1:53']
log_level = 5
use_syslog = true
ignore_system_dns = true
cache = true
[static]
[static.static_cloudflare]
# See https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v3/public-resolvers.md
# and
# https://adguard-dns.io/kb/miscellaneous/create-dns-stamp/
stamp = 'sdns://AgcAAAAAAAAABzEuMC4wLjEAEmRucy5jbG91ZGZsYXJlLmNvbQovZG5zLXF1ZXJ5'
DNSCrypt по-умолчанию берет DNS сервера из внешнего списка, который он запрашивает, используя системный DNS. Эти настройки указывают брать заданные адреса серверов (server_names) и не использовать системный DNS (ignore_system_dns). Для примера взят сервер CloudFlare в формате DNS stamp
У этого подхода есть большой минус: Контейнер app потеряет сеть, если wireguard контейнер перезагрузится. Способ вернуть app доступ в сеть через wireguard без перезагрузки app автору неизвестен.
P.S.
Слишком коротко для статьи, слишком длинно для поста.
Автор: user98675
