# 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](https://github.com/wg-easy/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 ```bash # 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.