Files
vpn/CLAUDE.md
T
davide 19d03ea624 docs(claude): document hardware compatibility and SBC constraints
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>
2026-05-07 08:10:43 +02:00

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 to 700 automatically by wg-init.
  • .env is gitignored.
  • The container requires NET_ADMIN and SYS_MODULE capabilities plus net.ipv4.ip_forward=1 sysctl — these are already set in docker-compose.yml.
  • The router must forward UDP port 51820 (or WG_PORT) to the server's local IP.
  • INSECURE=true is set in docker-compose.yml to 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.