Lightweight docker container providing an AmneziaWG VPN client
  • Dockerfile 59.2%
  • Shell 40.8%
Find a file
Dan Hollis 8a66f4b2db
All checks were successful
Build and Push / build (push) Successful in 1m25s
update version
2026-03-23 14:31:06 -04:00
.forgejo/workflows update version 2026-03-23 14:31:06 -04:00
.dockerignore initial commit 2026-03-12 21:17:29 -04:00
.gitignore initial commit 2026-03-12 21:17:29 -04:00
docker-compose.yml initial commit 2026-03-12 21:17:29 -04:00
Dockerfile initial commit 2026-03-12 21:17:29 -04:00
entrypoint.sh update entrypoint to find symlinks 2026-03-23 10:19:30 -04:00
LICENSE initial commit 2026-03-12 21:17:29 -04:00
README.md initial commit 2026-03-12 21:17:29 -04:00

AmneziaWG Client Docker

A lightweight Docker container for running AmneziaWG VPN client tunnels. Built from the latest amneziawg-tools and amneziawg-go sources with full support for all obfuscation parameters (Jc, Jmin, Jmax, S1-S4, H1-H4, I1-I5).

Quick Start

services:
  azwg-client:
    image: git.redteam.sh/dan/azwg-client:latest
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./config:/etc/amnezia/amneziawg:ro

Usage

  1. Place one or more AmneziaWG .conf files in the root of the directory mounted to /etc/amnezia/amneziawg (subdirectories are not scanned)
  2. Start the container - each config file creates an interface named after the file (azwg0.conf -> azwg0)
  3. Mount as :ro for immutable configs, or rw if using SaveConfig = true to persist runtime changes

Config Example

[Interface]
PrivateKey = <your-private-key>
Address = 10.0.0.2/32
DNS = 1.1.1.1
Jc = 5
Jmin = 49
Jmax = 998
S1 = 17
S2 = 110
H1 = 769445522
H2 = 87389456
H3 = 111129251
H4 = 1623403840

[Peer]
PublicKey = <server-public-key>
AllowedIPs = 0.0.0.0/0
Endpoint = server.example.com:51820
PersistentKeepalive = 25
PresharedKey = <your-pre-shared-key>

Requirements

  • Kernel module (recommended): Install amneziawg-linux-kernel-module on the host. If the module is already loaded on the host, no extra container config is needed. To let the container load it via modprobe, add SYS_MODULE capability and mount /lib/modules:/lib/modules:ro
  • Userspace fallback: If the kernel module is not available, the container automatically falls back to amneziawg-go (slower)

Healthcheck

No healthcheck is built into the image since VPN tunnels vary widely (full tunnel, split tunnel, private networks without internet, etc.). Define one in your compose file based on your setup:

# Full internet tunnel (AllowedIPs = 0.0.0.0/0)
healthcheck:
  test: ["CMD-SHELL", "ping -c1 -W3 1.1.1.1 >/dev/null 2>&1"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 30s

# Split tunnel to a private subnet
healthcheck:
  test: ["CMD-SHELL", "ping -c1 -W3 10.0.0.1 >/dev/null 2>&1"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 30s

# Just verify the interface is up (any tunnel type)
healthcheck:
  test: ["CMD-SHELL", "awg show interfaces | grep -q ."]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 30s

Capabilities & Volumes

Only NET_ADMIN is strictly required. Everything else depends on your environment:

Item Required? When needed
cap_add: NET_ADMIN Always Create and manage network interfaces
cap_add: SYS_MODULE No Only if the container needs to load the amneziawg kernel module (not needed if already loaded on host)
/lib/modules:/lib/modules:ro No Same as above - provides module files for modprobe
/dev/net/tun:/dev/net/tun Recommended Required for the amneziawg-go userspace fallback (TUN device). Not technically needed when using the kernel module (which creates its own interface type), but strongly recommended since the container falls back to userspace silently if the module isn't loaded.
net.ipv4.conf.all.src_valid_mark=1 No Only needed if your config routes all traffic (AllowedIPs = 0.0.0.0/0). awg-quick sets this automatically, but declaring it as a sysctl ensures it is set before interface creation. Not needed for split-tunnel configs.

Full example (all options included)

services:
  azwg-client:
    image: git.redteam.sh/dan/azwg-client:latest
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./config:/etc/amnezia/amneziawg:ro
      - /lib/modules:/lib/modules:ro

Routing Other Containers Through the VPN

Use Docker's network_mode: service: to route another container's traffic through the VPN tunnel. The dependent container shares the VPN container's network stack, so all its traffic goes through AmneziaWG.

services:
  azwg-client:
    image: git.redteam.sh/dan/azwg-client:latest
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    volumes:
      - ./config:/etc/amnezia/amneziawg:ro
    healthcheck:
      test: ["CMD-SHELL", "awg show interfaces | grep -q . && ping -c1 -W3 1.1.1.1 >/dev/null 2>&1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

  myapp:
    image: myapp:latest
    network_mode: "service:azwg-client"
    depends_on:
      azwg-client:
        condition: service_healthy
    restart: unless-stopped

Note: When using network_mode: service:, any ports you need to access on the dependent container must be published on the VPN container instead, since they share the same network namespace.

How It Works

  • On startup, all .conf files from /etc/amnezia/amneziawg/ are brought up via awg-quick
  • awg-quick adds policy routing rules when AllowedIPs = 0.0.0.0/0 (fwmark-based routing to prevent loops), but does not add NAT/masquerade rules — use PostUp/PostDown in your config if you need those
  • On SIGTERM/SIGINT, all interfaces are cleanly torn down via awg-quick down

Building

docker build -t azwg-client:latest .

License

This project is licensed under the GPL-3.0 License - see the LICENSE file for details.