AmneziaWG rootless server
518
More details in the Github repo
The project started when the well-known image https://hub.docker.com/r/metaligh/amneziawg stopped working with the latest version of the AmneziaWG kernel module, and its source code could not be found in public access. Then I decided to rebuild it from scratch and if you are rebuilding something from scratch - why not to try to make it rootless right away.
Note about image's size: its base part is less than 40mb, but coredns component adds itself like ~70mb more - however, it's absolutely optional if you're not using AWG-server for name resolution.
podman pull amneziawg-rootless:latest
podman volume create amneziawg-cfg
podman run --detach --name awg-rootless --publish 3400:51820/udp -v amneziawg-cfg:/etc/amnezia/amneziawg/ \
--cap-add net_admin --sysctl net.ipv4.conf.all.src_valid_mark=1 --sysctl net.ipv4.ip_forward=1 \
--env SERVERURL="some.lab.host" --env SERVERPORT="4430" amneziawg-rootless:latest
podman exec -it awg-rootless /bin/bash
menu.sh
or build an image by yourself:
git clone https://github.com/andrico21/awneziawg-rootless.git
cd awneziawg-rootless
podman build . -t awneziawg-rootless --no-cache --squash-all
podman image ls | grep amneziawg-rootless
amneziawg kernel module installed on host- check https://github.com/amnezia-vpn/amneziawg-linux-kernel-moduleamneziawg loaded: lsmod | grep '^amneziawg' to check (should return something, yep)sudo modprobe amneziawg - to load manually onceecho amneziawg | sudo tee /etc/modules-load.d/amneziawg.conf - to make it loaded automatically on boot (recommended)SERVERURL (string): your public server name, reflected in user configsSERVERPORT (string): your public server AWG-port, reflected in user configsASC_Jc, ASC_Jmin, ASC_Jmax, ASC_S1, ASC_S2, ASC_H1, ASC_H2, ASC_H3, ASC_H4. If not defined - hardcoded default values are used. There's a validation procedure inside to maintain logic described in https://docs.amnezia.org/documentation/amnezia-wg/ document. In case of changing this group of parameters you have to regenerate server configuration.WG_INTERNAL_SUBNET (optional, CIDR string): you can define custom tunnel-subnet - server configuration reset is required to apply new network settingsWG_CUSTOM_DNS (optional, string): if not specified - container address will be used for new user configs. But it's possible to specify DNS servers like "8.8.8.8,1.1.1.1"DNS_BUILTIN (optional, string): if true - start builtin coredns server, false by default.WG_CUSTOM_MTU (optional, integer): by default, it's set to 1280 for new users, if need other - set this.WG_KEEPALIVE_SEC (optional, integer): default keepalive interval for new users. 0 by default.EXPORT2JSON (optional, string): if true - currently like placeholder for future improvements, makes daily users list export to /tmp/awgusers.json inside of container, false by default.You can use the classic way and run container under host root user - even in this case it will be a way more confined than other implementations, just mind that volume location will be in /var/lib/containers/ instead of user homedir.
Create new user without sudo-permissions (lingering is required to be enabled if you will run it using quadlets):
adduser awguser --shell /bin/false --disabled-password --comment "Rootless user to run AmneziaWG containers" \
&& loginctl enable-linger awguser
Run a shell with new user and configure XDG_RUNTIME_DIR variable (needed for systemctl --user commands)
sudo -u awguser /bin/bash
echo "export XDG_RUNTIME_DIR=/run/user/$(id -u)" >> ~/.bashrc; export XDG_RUNTIME_DIR=/run/user/$(id -u)
mkdir -p ~/.config/containers/systemd/
Next, in the same shell pull an image, create volume (directory bind is also fine) and run.
Use an external address of your AWG-server as SERVERURL and externally available port as SERVERPORT. Inside of container application always uses the standard 51820/udp port - so, you have to only define an external one for user-related configurations.
podman pull amneziawg-rootless:latest
podman volume create amneziawg-cfg
podman run --detach --name awg-rootless --publish 4450:51820/udp \
--mount=type=volume,source=amnez-test,destination=/etc/amnezia/amneziawg/,readonly=false,nodev,noexec,nosuid,chown=true \
--cap-drop all --cap-add net_admin --security-opt no-new-privileges --read-only \
--sysctl net.ipv4.conf.all.src_valid_mark=1 --sysctl net.ipv4.ip_forward=1 \
--env SERVERURL="myserver.google.com" --env SERVERPORT=4450 --env DNS_BUILTIN="true" \
--env WG_INTERNAL_SUBNET="192.168.254.0/24" --env WG_CUSTOM_MTU=1100 amneziawg-rootless:latest
Container logs:
podman logs -f awg-rootless
Interactive menu to work with configs. Don't forget to reload AmneziaWG configuration after adding users (menu item #6).
podman exec -it awg-rootless menu.sh
Configs are available in /home/awguser/.local/share/containers/storage/volumes/amneziawg-cfg/_data/ directory, but since the container operates under rootless user subuid - its files are not available directly, use podman unshare to retrieve them (text config + its generated QR code representation):
ls -l /home/awguser/.local/share/containers/storage/volumes/amneziawg-cfg/_data/peers/
ls: cannot open directory '/home/awguser/.local/share/containers/storage/volumes/amneziawg-cfg-demo/_data/peers/': Permission denied
podman unshare ls -l /home/awguser/.local/share/containers/storage/volumes/amneziawg-cfg/_data/peers/
total 8
-rw-r----- 1 65500 65500 453 Nov 16 13:05 test.conf
-rw-r----- 1 65500 65500 1540 Nov 16 13:05 test.conf.png
podman unshare cp .local/share/containers/storage/volumes/amneziawg-cfg/_data/peers/test.conf* ~/
or easier to retrieve them from container (test user is created for demo):
podman exec -it awg-rootless /bin/bash
1203ac6555aa:/etc/amnezia/amneziawg$ cat peers/test.conf
# test
# CreatedAt: 2025-11-16T13:05:44Z
[Interface]
...
I prefer to run podman containers via systemd quadlets, create user-scoped container-unit in /home/awguser/.config/containers/systemd/ directory. I left some optional parameters commented for general awareness:
# /home/awguser/.config/containers/systemd/amneziawg.container
[Container]
ContainerName=awg-rootless
Environment=SERVERURL=myserver.google.com SERVERPORT=4450
Image=amneziawg-rootless:latest
#PodmanArgs=--cpus 0.25 --memory 45mb
#Pull=newer
# networking
PublishPort=4450:51820/udp
Sysctl=net.ipv4.conf.all.src_valid_mark=1 net.ipv4.ip_forward=1
# storage
Mount=type=volume,source=amnez-test,destination=/etc/amnezia/amneziawg/,readonly=false,nodev,noexec,nosuid,chown=true
# security
AddCapability=net_admin
DropCapability=all
NoNewPrivileges=true
ReadOnly=true
#SeccompProfile=/usr/share/containers/awg_server_coredns.json
[Install]
WantedBy=default.target
[Unit]
Description=AmneziaWG server (rootless)
Wants=podman-user-wait-network-online.service
After=podman-user-wait-network-online.service
[Service]
Restart=on-failure
RestartSec=10s
Save and reload user daemons:
systemctl --user daemon-reload && systemctl --user status awg-rootless.service
systemctl --user start awg-rootless.service
systemctl --user status awg-rootless.service
Running under host root - test container internal user is mapped to host rootless user's uid, only NET_ADMIN capability is added for user namespace:
root@awg-test:~# podman top awg-rootless comm args pid hpid user huser group hgroup capeff capamb capbnd capinh capprm
COMMAND COMMAND PID HPID USER HUSER GROUP HGROUP EFFECTIVE CAPS AMBIENT CAPS BOUNDING CAPS INHERITED CAPS PERMITTED CAPS
entrypoint.sh /bin/sh /entrypoint.sh 1 20863 awg 65500 awg 65500 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
coredns coredns -conf /etc/coredns/Corefile 2 20865 awg 65500 awg 65500 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
entrypoint.sh /bin/sh /entrypoint.sh 3 20866 awg 65500 awg 65500 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
entrypoint.sh /bin/sh /entrypoint.sh 5 20868 awg 65500 awg 65500 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
sleep sleep 86400 15 20878 awg 65500 awg 65500 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
sleep sleep 86400 18 20881 awg 65500 awg 65500 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
Running under host rootless user - container internal user is mapped to host rootless user' subuid range, only NET_ADMIN capability is added for user namespace
podman top awg-rootless args pid hpid user huser group hgroup capeff capamb capbnd capinh capprm
COMMAND PID HPID USER HUSER GROUP HGROUP EFFECTIVE CAPS AMBIENT CAPS BOUNDING CAPS INHERITED CAPS PERMITTED CAPS
/bin/sh /entrypoint.sh 1 20399 awg 231035 awg 231035 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
coredns -conf /etc/coredns/Corefile 2 20401 awg 231035 awg 231035 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
/bin/sh /entrypoint.sh 3 20402 awg 231035 awg 231035 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
/bin/sh /entrypoint.sh 6 20405 awg 231035 awg 231035 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
sleep 86400 14 20413 awg 231035 awg 231035 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
sleep 86400 17 20416 awg 231035 awg 231035 NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN NET_ADMIN
Automatic reusing of IP addresses for new users if there're released ones after user deletion.
Config reinitialization is supported with automatic backup to avoid accidental loss.
podman exec awg-rootless awg_create_server_config.sh --force
podman exec awg-rootless ls -l /etc/amnezia/amneziawg/backup/
total 8
-r--r----- 1 awg awg 749 Nov 16 17:06 backup_20251116_170618.tar.bz2
-r--r----- 1 awg awg 754 Nov 16 17:07 backup_20251116_170742.tar.bz2
Generate json with users list
podman exec awg-rootless awg_users2json.sh
Pre-generated seccomp-profile is available in seccomp-profiles/ directory - still in beta, but so far no problems.
Copy to /usr/share/containers/awg_server_coredns.json, then specify it for container using --security-opt seccomp=/usr/share/containers/awg_server_coredns.json (or use SeccompProfile directive in systemd container unit)
I spent several days to make it working with pasta/passt, but eventually discovered that it just doesn't work properly with Ubuntu 24.04's stock pasta, but works with the latest one compiled from sources - see issue https://github.com/containers/podman/issues/27541
podman run --detach --replace --name amnez-test --rm --network "pasta:-I,tap0,-U,auto,-u,3400:51820" -v amnez-test:/etc/amnezia/amneziawg/ --cap-add net_admin --sysctl net.ipv4.conf.all.src_valid_mark=1 --sysctl net.ipv4.ip_forward=1 amneziawg-rootless:latest
It works fine with slirp4netns, but more perfomance is expected with pasta (ideally, lol)
Content type
Image
Digest
sha256:1e827eaf0…
Size
35.9 MB
Last updated
about 1 month ago
docker pull andrico21/amneziawg-rootless