16 KiB
AmneziaVPN в Docker: полный гайд
Цель: поднять в Docker контейнере клиент AmneziaVPN (AmneziaWG 3.1), подключить его по пользовательскому .conf от Amnezia Premium и сделать его VPN-шлюзом для домашней сети.
Автор: Hermes Agent • Среда: LXC-контейнер (Docker 29.4.1, ядро Proxmox 6.17.13-2-pve)
1. Что потребуется
| Компонент | Значение |
|---|---|
| Хост | Linux с Docker 20.10+ (тестировалось: LXC 29.4.1) |
/dev/net/tun |
Должен существовать и быть открыт |
ip_forward |
1 |
.conf файл |
Amnezia Premium / AmneziaWG 3.1 конфиг |
| Версии | amneziawg-go v3.1.20260828, amneziawg-tools v3.1.20260812 |
Пример .conf (Amnezia Premium, конфиг 3.1):
[Interface]
Address = 100.85.175.118/32
DNS = 100.64.0.1, 8.8.4.4
PrivateKey = fSdjHMi8tAzL2wbNxsylQW1FAIXdn2/ODPGWr569J/w=
Jc = 5
Jmin = 10
Jmax = 80
H1 = 1
H2 = 2
H3 = 3
H4 = 4
HeaderProtectionKey = 8H1SggRhZ8iT8ZYZlS/RPf6SItoxb8OeXX2rNwMgc3M=
RekeyAfterTime = 100-120
RekeyTimeout = 3-8
RejectAfterTime = 150-180
KeepaliveTimeout = 7-13
MaxHandshakeAttempts = 15-20
ContentPaddingAddition = 10-100
[Peer]
PublicKey = FfgUO6z0plGUntTW3T8fsFsZ250GcQZEKlCgj1fkRFc=
PresharedKey = +Is1eWUUQ5UXYD8E7zigG32KaThcGC/YolOVS7LeDGU=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 78.17.149.78:8344
PersistentKeepalive = 25-35
⚠️ НЕ выкладывай
.confни в чат, ни на сторонние сайты. Его приватный ключ — твой доступ. Файл копируется только на сервер.
2. Подготовка хоста
# Проверить Tun-устройство и форвардинг
ls -la /dev/net/tun
cat /proc/sys/net/ipv4/ip_forward # должно быть 1
# Включить форвардинг (если 0)
echo 1 > /proc/sys/net/ipv4/ip_forward
# Проверить Docker
docker --version
На LXC (виртуальном контейнере Proxmox) убедись, что:
/dev/net/tunсуществует (создаётся через настройки LXC)net.ipv4.ip_forward=1установлен (в Proxmox: LXC → Options → Features)
3. Скачивание репозиториев
⚠️ Сборку ведём из локальных копий репозиториев (COPY в Dockerfile), так как DNS внутри build-контейнера нестабилен.
mkdir -p /root/awg-src
cd /root/awg-src
# Правильный DNS на хосте LXC
echo "nameserver 8.8.8.8" > /etc/resolv.conf
git clone --depth 1 --branch v3.1.20260828 https://github.com/amnezia-vpn/amneziawg-go.git
git clone --depth 1 --branch v3.1.20260812 https://github.com/amnezia-vpn/amneziawg-tools.git
Проверка тегов (актуальные на момент сборки):
git ls-remote --tags https://github.com/amnezia-vpn/amneziawg-go.git | tail
# v3.1.20260813, v3.1.20260814, v3.1.20260828
git ls-remote --tags https://github.com/amnezia-vpn/amneziawg-tools.git | tail
# v3.0.20260805, v3.1.20260812
Найдите последний тег v3.1.* — они выходят часто и содержат исправления DPI-обхода.
4. Директория сборки
mkdir -p /root/awg-builder
cp -r /root/awg-src/amneziawg-go /root/awg-builder/
cp -r /root/awg-src/amneziawg-tools /root/awg-builder/
5. Dockerfile
/root/awg-builder/Dockerfile
# syntax=docker/dockerfile:1
FROM golang:alpine AS go-build
RUN apk add --no-cache make gcc musl-dev linux-headers
COPY amneziawg-go /src/amneziawg-go
RUN cd /src/amneziawg-go && make && cp amneziawg-go /go/bin/
COPY amneziawg-tools /src/amneziawg-tools
RUN cd /src/amneziawg-tools/src && make && cp wg /go/bin/awg
FROM alpine:3.21
RUN apk add --no-cache iptables ip6tables iproute2 bash openresolv
COPY --from=go-build /go/bin/amneziawg-go /usr/bin/
COPY --from=go-build /go/bin/awg /usr/bin/
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Почему
cp wg /go/bin/awg: вamneziawg-toolsбинарь надёжнее назватьawg. Это клиентская утилита управления интерфейсом.
Healthcheck (необязателен): добавить в Dockerfile HEALTHCHECK — можно пропустить, статус смотрим через awg show.
6. entrypoint.sh
Скрипт: поднимает интерфейс, загружает конфиг (с фолбэком на минимальный), настраивает policy routing и NAT.
/root/awg-builder/entrypoint.sh
#!/bin/bash
CONFIG_DIR=/config
for f in "${CONFIG_DIR}"/*.conf; do
[ -f "$f" ] || continue
IFACE=$(basename "$f" .conf)
echo "Starting AmneziaWG interface: ${IFACE}"
modprobe amneziawg 2>/dev/null || true
if ! ip link show "${IFACE}" 2>/dev/null | grep -q UP; then
ip link add "${IFACE}" type amneziawg 2>/dev/null || {
echo "[*] Using userspace amneziawg-go"
amneziawg-go "${IFACE}" 2>/dev/null || true
sleep 1
}
fi
TMPF=$(mktemp)
grep -v -E '^(Address|DNS)' "$f" > "$TMPF"
if ! awg setconf "${IFACE}" "$TMPF" 2>/dev/null; then
echo "[!] Trying minimal config"
{
echo "[Interface]"
grep -E '^PrivateKey' "$f"
echo "ListenPort = 51820"
echo ""
echo "[Peer]"
grep -E '^(PublicKey|PresharedKey|AllowedIPs|Endpoint|PersistentKeepalive)' "$f"
} | awg setconf "${IFACE}" /dev/stdin 2>/dev/null || {
echo "[!] FATAL: cannot parse config"
rm -f "$TMPF"
continue
}
fi
rm -f "$TMPF"
IP=$(grep ^Address "$f" | head -1 | cut -d= -f2 | tr -d ' ' | cut -d, -f1)
[ -n "$IP" ] && ip addr add "$IP" dev "${IFACE}" 2>/dev/null || true
ip link set "${IFACE}" up
echo "[*] ${IFACE} UP: ${IP}"
done
# --- Policy routing: только forwarded LAN-трафик через VPN ---
mkdir -p /etc/iproute2
if ! grep -q "awg-vpn" /etc/iproute2/rt_tables 2>/dev/null; then
echo "100 awg-vpn" >> /etc/iproute2/rt_tables 2>/dev/null || true
fi
ip route add 0.0.0.0/0 dev awg0 table 100 2>/dev/null || true
# Пометить forwarded LAN-трафик (не на LAN) меткой 100
iptables -t mangle -C PREROUTING -s 192.168.1.0/24 ! -d 192.168.1.0/24 -j MARK --set-mark 100 2>/dev/null || \
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 ! -d 192.168.1.0/24 -j MARK --set-mark 100 2>/dev/null || true
# Пакеты с меткой 100 → таблица awg-vpn (через awg0)
ip rule add fwmark 100 lookup 100 priority 5000 2>/dev/null || true
# NAT для LAN через туннель
iptables -t nat -C POSTROUTING -s 192.168.1.0/24 -o awg0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o awg0 -j MASQUERADE 2>/dev/null || true
echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true
echo "[*] AmneziaWG client running"
awg show 2>/dev/null || true
sleep infinity
Как работает policy routing:
- Локальный (исходящий) трафик самого LXC (SSH и т.д.) → идёт напрямую через
eth0- Только forwarded трафик из LAN → помечается меткой → направляется в таблицу
awg-vpn→ уходит черезawg0- Это сохраняет доступ к управлению LXC, не ломая VPN-шлюз
7. Редактирование сети под свою подсеть
В скрипте выше я использовал 192.168.1.0/24 — это основной LAN сегмент, который пойдёт через VPN.
Если нужна отдельная VPN-подсеть (например 192.168.2.0/24), замени в двух местах:
iptables -t mangle ... -s 192.168.1.0/24→-s 192.168.2.0/24iptables -t nat ... -s 192.168.1.0/24 ... -o awg0→-s 192.168.2.0/24
Подробнее про отдельную подсеть см. раздел «Расширенный сценарий» ниже.
8. Запуск
- Создай директорию конфига и положи туда
.conf:
mkdir -p /root/awg-config
cp /путь/к/de.conf /root/awg-config/awg0.conf
# ↑ имя файла = имя интерфейса (awg0)
- Собери образ:
cd /root/awg-builder
docker build -t amneziawg-client:latest .
- Запусти контейнер:
docker run -dit \
--name=amneziawg-client \
-v /root/awg-config:/config \
--network=host \
--device=/dev/net/tun:/dev/net/tun \
--cap-add=NET_ADMIN \
--cap-add=SYS_MODULE \
--restart always \
amneziawg-client:latest
- Проверь:
docker ps --filter name=amneziawg-client # Up, restart always
docker logs amneziawg-client # интерфейс UP, handshake
docker exec amneziawg-client awg show # самый наглядный статус
9. Проверка работоспособности
# Статус интерфейса и handshake (главное)
docker exec amneziawg-client awg show
# Должно быть: "latest handshake: N seconds ago" и ненулевые transfer
# Внешний IP с самого LXC (через awg0):
curl -s ifconfig.me
# Это будет IP VPN-провайдера
# Статус NAT/маршрутизации
docker exec amneziawg-client iptables -t mangle -L PREROUTING -n -v
docker exec amneziawg-client iptables -t nat -L POSTROUTING -n -v
# Проверка forwarded трафика из LAN:
# с устройства в 192.168.1.0/24, у которого шлюз = 192.168.1.147
curl -s ifconfig.me
# Тоже IP VPN-провайдера
Полезный признак нормальной работы:
peer: FfgUO6z0plGUntTW3T8fsFsZ250GcQZEKlCgj1fkRFc=
latest handshake: 42 seconds ago ← периодически обновляется
transfer: 1.24 MiB received, 3.2 MiB sent ← данные идут
Если latest handshake отсутствует и received = 0 — пакеты не доходят до сервера. Причины: файрвол роутера блокирует UDP, неверный Endpoint в .conf, фильтрация DPI.
10. Включение шлюза для LAN
Когда туннель поднят, чтобы устройства LAN пошли через VPN, на роутере меняют шлюз по умолчанию или статический маршрут:
Keenetic (веб-интерфейс):
Интернет-центр → Маршрутизация → Статические маршруты- Добавить:
- Сеть назначения:
0.0.0.0 - Маска:
0.0.0.0 - Шлюз:
192.168.1.147 - (или конкретная VPN-подсеть, если не вся LAN)
- Сеть назначения:
На телефоне (временная проверка):
- Wi-Fi → IP-настройки → Статический → Маршрутизатор =
192.168.1.147
11. Питфоллы и их решение
| Проблема | Причина | Решение |
|---|---|---|
Unknown device type. Falling back to userspace |
Нет модуля ядра amneziawg | Нормально; userspace (amneziawg-go) работает |
Line unrecognized: S3=145 |
Старая версия awg, не знает v3.1 параметры |
Собрать свежую amneziawg-tools v3.1.x (п.3) |
Address=/DNS= line error |
awg setconf не понимает wg-quick директивы |
Убрать Address/DNS из setconf (см. entrypoint) |
/proc/sys/...ip_forward: Read-only |
Ограничение LXC / контейнера | Включить заранее на хосте (п.2) |
Диск забит (no space left on device) |
Сборки и образы жрут место | docker system prune, расширить диск |
gcc not found при сборке tools |
Нет компилятора | apk add gcc musl-dev в Dockerfile |
linux/types.h нет |
Нужны linux-headers | apk add linux-headers |
cockpit/быстрые списки GitHub таймаут |
DNS внутри build-контейнера | Клонировать на хосте, COPY в Dockerfile |
12. Расширенный сценарий: отдельная VPN-подсеть (вариант B)
Нужно, чтобы не вся LAN уходила в VPN, а только отдельные устройства в своей подсети (192.168.2.0/24).
На LXC добавить alias-IP шлюза:
ip addr add 192.168.2.1/32 dev eth0
# persistence: netplan / LXC interface
entrypoint.sh — расширить маршрутизацию:
# Вторая подсеть → тоже через VPN
iptables -t mangle -A PREROUTING -s 192.168.2.0/24 ! -d 192.168.2.0/24 -j MARK --set-mark 100
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o awg0 -j MASQUERADE
DHCP сервер для 192.168.2.0/24 (dnsmasq на LXC или Docker):
interface=eth0
dhcp-range=192.168.2.100,192.168.2.200,255.255.255.0,24h
dhcp-option=3,192.168.2.1
dhcp-option=6,100.64.0.1,8.8.8.8
Keenetic — статический маршрут:
- Сеть:
192.168.2.0, маска255.255.255.0, шлюз192.168.1.147
Keenetic — гостевая сеть с другой подсетью:
- Создать вторую SSID, IP
192.168.2.1/24, DHCP192.168.2.x
13. Обновление версий AWG
Поскольку AmneziaWG 3.x развивается быстро (новые теги), при проблемах с DPI-блокировками:
# Узнать свежие теги
git ls-remote --tags https://github.com/amnezia-vpn/amneziawg-go.git | tail
git ls-remote --tags https://github.com/amnezia-vpn/amneziawg-tools.git | tail
# Пересобрать из свежих копий
cd /root/awg-src/amneziawg-go && git fetch --tags && git checkout v3.1.XXXXXXX
cd /root/awg-src/amneziawg-tools && git fetch --tags && git checkout v3.1.XXXXXXX
cp -r /root/awg-src/amneziawg-*/* /root/awg-builder/
docker build -t amneziawg-client:latest /root/awg-builder/
14. Безопасность и данные
- Приватный ключ в
.confне попадает в логи/память ассистента — только в файл на сервере. - Не выкладывай
.confв чат, репозитории, скриншоты. .confчитается из/root/awg-config/awg0.confтолько контейнером.- Права:
chmod 600 /root/awg-config/awg0.conf.
Итоговая структура файлов
/root/awg-config/awg0.conf ← твой .conf (private)
/root/awg-builder/Dockerfile
/root/awg-builder/entrypoint.sh
/root/awg-builder/amneziawg-go/ ← исходники (3.1.20260828)
/root/awg-builder/amneziawg-tools/ ← исходники (3.1.20260812)
/root/awg-src/ ← свежие клоны (для апдейтов)
Контейнер: amneziawg-client = полностью автономный AWG-шлюз, ставится как --restart always, не зависит от остальных Docker-сервисов.