Files
vpn/CLAUDE.md
T
davide fc5d6209c1 docs: add resource limits guide for low-RAM devices
- README: new section explaining WG_MEM_LIMIT / WG_MEMSWAP_LIMIT with
  per-RAM-tier values and host swap configuration for SBC boards
- CLAUDE.md: simplify resource limits table, drop device-specific
  measurements, reference README for per-board guidance
- .env.example: update comments with per-tier values and OOM warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 08:22:43 +02:00

3.3 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 (override via .env):

Variable Default Notes
WG_MEM_LIMIT 256m Hard cap for the wg-easy container
WG_MEMSWAP_LIMIT 256m Keep equal to WG_MEM_LIMIT to disable container swap
WG_CPUS 1.0 0.75 on single-core boards (Pi Zero, Pi 1)

The limit exists to prevent Node.js from slowly growing over long uptime and triggering the host OOM-killer (symptom: SSH becomes unreachable). Do not go below 96m or the runtime OOM-kills on startup. See README §Dispositivi a risorse limitate for per-board guidance and swap configuration.