Add a dedicated section covering: - tested architectures: arm64 (RPi 3/4/5, Orange Pi, Rock Pi), armv7, amd64 (x86_64 servers, VMs, Intel NUC) - known ip6tables boot issue on hosts with BSP/minimal kernels and how wg-init solves it automatically across all architectures - resource limit variables (WG_MEM_LIMIT, WG_MEMSWAP_LIMIT, WG_CPUS) with guidance for boards under 1 GB RAM Also corrects stale wg0.json reference to wg-easy.db in constraints list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A Docker Compose setup for a self-hosted WireGuard VPN server using wg-easy, designed to run on a Raspberry Pi or any Linux server. The web UI (wg-easy) handles client management, QR code generation, and key lifecycle.
Common commands
# First-time setup
cp .env.example .env
# Edit .env: set TZ, optionally WG_PORT / WG_UI_PORT
# Start the VPN
docker compose up -d
# Update wg-easy to the latest image
docker compose pull && docker compose up -d
# View logs
docker compose logs -f wg-easy
Configuration
.env (not committed) controls only infrastructure-level settings:
| Variable | Description |
|---|---|
TZ |
IANA timezone (e.g. Europe/Rome) |
WG_PORT |
UDP VPN port (default 51820) |
WG_UI_PORT |
Web UI port (default 51821) |
v15+: Host, password, and DNS are configured through the web UI wizard on first launch — not via environment variables.
Important constraints
wg-data/is auto-generated by the container on first start and holds live WireGuard keys (wg0.conf,wg-easy.db). Never commit it. Permissions are set to700automatically bywg-init..envis gitignored.- The container requires
NET_ADMINandSYS_MODULEcapabilities plusnet.ipv4.ip_forward=1sysctl — these are already set indocker-compose.yml. - The router must forward UDP port 51820 (or
WG_PORT) to the server's local IP. INSECURE=trueis set indocker-compose.ymlto allow HTTP access on the local network.
SBC / resource-constrained devices
Works on any Linux host with a 64-bit kernel and WireGuard support. Tested architectures:
| Hardware | Arch | Notes |
|---|---|---|
| Raspberry Pi 5 | arm64 | kernel 6.12.x+rpt-rpi-2712 |
| Raspberry Pi 4 / 3B+ | arm64 / armv7 | 32-bit kernels need armv7 image |
| Orange Pi, Rock Pi, Banana Pi | arm64 | depends on board BSP kernel |
| Generic x86_64 server / VM | amd64 | standard Debian/Ubuntu/Fedora |
| Intel NUC / mini-PC | amd64 | same as above |
Known issue — ip6tables modules not loaded at boot.
Affects mostly SBC boards with custom BSP kernels, but can occur on any host where ip6_tables and ip6table_nat are not auto-loaded. Without them wg-quick up wg0 fails and rolls back, leaving no wg0 interface. Symptom: every API call returns Command failed: wg show wg0 dump / No such device.
The wg-init service handles this automatically: it runs modprobe ip6_tables ip6table_nat (with SYS_MODULE cap and /lib/modules bind-mounted read-only) before wg-easy starts. Failures are silenced (|| true) so the setup works on kernels where these modules are built-in or unavailable.
Resource limits (docker-compose.yml):
| Variable | Default | Purpose |
|---|---|---|
WG_MEM_LIMIT |
256m |
Hard memory cap for wg-easy |
WG_MEMSWAP_LIMIT |
256m |
Disables swap (swap = mem limit) |
WG_CPUS |
1.0 |
CPU share (1 core) |
Lower WG_MEM_LIMIT to 128m on boards with less than 1 GB RAM. Do not set it below 96m or the Node.js runtime will OOM-kill on startup.