The linuxserver/wireguard image runs a WireGuard server in a container and auto-generates a
config (and QR) for each peer you name — no manual key juggling.
docker-compose.yml
services:
wireguard:
image: lscr.io/linuxserver/wireguard:latest
container_name: wireguard
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- SERVERURL=auto # or your DDNS hostname, e.g. vpn.example.com
- SERVERPORT=51820
- PEERS=laptop,phone # names (or a number like 3)
- PEERDNS=auto
- INTERNAL_SUBNET=10.13.13.0
- ALLOWEDIPS=0.0.0.0/0 # full tunnel; use subnets for split tunnel
ports:
- "51820:51820/udp"
volumes:
- ./config:/config
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
Bring it up
docker compose up -d
docker compose logs -f wireguard
Get a peer’s config and QR
Generated files land in ./config/peer_laptop/. To print a QR in the terminal:
docker exec wireguard /app/show-peer laptop
Scan it in the WireGuard app, or copy ./config/peer_laptop/peer_laptop.conf to a desktop client.
Add a peer later
Edit PEERS (add the new name) and recreate the container:
docker compose up -d --force-recreate
Alternative: wg-easy (web UI)
If you’d rather click than edit YAML, ghcr.io/wg-easy/wg-easy gives a web admin panel to add
peers and show QR codes. Map its UI port and WireGuard’s UDP port, set WG_HOST to your public
hostname, and follow its current README for the password/auth variable (it changes between
versions).
Verify
docker exec wireguard wg show # recent handshake once a client connects
Host needs UDP 51820 open (firewall / cloud security group / router port-forward) and the
WireGuard kernel module available — modern kernels include it. SERVERURL=auto tries to detect
your public IP; set it explicitly to a DDNS name if detection is wrong or your IP changes.