A lightweight, production-ready Go application that automatically synchronizes port forwarding changes from Gluetun VPN to qBittorrent. Built with observability and reliability in mind.
- Automatic Port Synchronization: Monitors Gluetun's forwarded port file and updates qBittorrent instantly
- Full Observability: Prometheus metrics for monitoring and alerting
- Health & Readiness: Kubernetes-compatible health check endpoints
- Efficient File Watching: Uses fsnotify for real-time file system events
- Secure by Default: Runs as non-root user in Docker, minimal attack surface
- Lightweight: ~15MB Docker image, minimal resource footprint
- Production Ready: Automatic re-authentication, graceful error handling, fallback polling
An example docker-compose.yml file is available at docker-compose.example.yml.
docker run -d \
--name forwardarr \
-e GLUETUN_PORT_FILE=/tmp/gluetun/forwarded_port \
-e TORRENT_CLIENT_URL=http://qbittorrent:8080 \
-e TORRENT_CLIENT_USER=admin \
-e TORRENT_CLIENT_PASSWORD=adminadmin \
-v gluetun-data:/tmp/gluetun:ro \
-p 9090:9090 \
ghcr.io/eslutz/forwardarr:latestAll configuration is done via environment variables. An example configuration file is available at docs/.env.example.
| Variable | Default | Description |
|---|---|---|
GLUETUN_PORT_FILE |
/tmp/gluetun/forwarded_port |
Path to Gluetun's port file |
TORRENT_CLIENT_URL |
http://localhost:8080 |
Torrent client WebUI address |
TORRENT_CLIENT_USER |
admin |
Torrent client username |
TORRENT_CLIENT_PASSWORD |
adminadmin |
Torrent client password |
SYNC_INTERVAL |
300 |
Fallback polling interval (seconds). Set to 0 to disable periodic sync. |
METRICS_PORT |
9090 |
HTTP server port for metrics/health |
LOG_LEVEL |
info |
Logging level (debug, info, warn, error) |
┌─────────┐ ┌──────────┐ ┌────────────┐
│ Gluetun │ writes │ Port │ watched │ │
│ VPN ├────────►│ File ├────────►│ Forwardarr │
└─────────┘ └──────────┘ └─────┬──────┘
│
│ updates
▼
┌─────────────┐
│ qBittorrent │
└─────────────┘How it works:
- Gluetun establishes a VPN connection with port forwarding
- Gluetun writes the forwarded port to a file
- Forwardarr watches this file for changes using fsnotify
- When the port changes, Forwardarr updates qBittorrent's listening port via API
- A fallback ticker ensures sync even if file events are missed (configurable, can be disabled)
| Endpoint | Purpose | Response |
|---|---|---|
GET /health |
Liveness probe | 200 OK if running |
GET /ready |
Readiness probe | 200 OK if qBittorrent is reachable |
GET /status |
Full diagnostics | JSON status object |
GET /metrics |
Prometheus metrics | Metrics in OpenMetrics format |
- /health: Configure this as a Liveness Probe. It indicates if the Forwardarr process is running. If this fails, the container should be restarted.
- /ready: Configure this as a Readiness Probe. It indicates if Forwardarr can successfully communicate with qBittorrent. If this fails, the container should remain running but not receive traffic/work until the dependency recovers.
- /status: Use this for manual debugging or external monitoring dashboards. It provides a JSON snapshot of the application's internal state, including version and connectivity status.
- /metrics: Configure your Prometheus scraper to target this endpoint to collect application performance data.
| Metric | Type | Description |
|---|---|---|
forwardarr_info |
Gauge | Build information (version, commit, date) |
forwardarr_current_port |
Gauge | Current forwarded port from Gluetun |
forwardarr_sync_total |
Counter | Total number of successful port syncs |
forwardarr_sync_errors |
Counter | Total number of failed sync attempts |
forwardarr_last_sync_timestamp |
Gauge | Unix timestamp of last successful sync |
# Current forwarded port
forwardarr_current_port
# Sync success rate (last 5m)
rate(forwardarr_sync_total[5m]) / (rate(forwardarr_sync_total[5m]) + rate(forwardarr_sync_errors[5m]))
# Time since last successful sync
time() - forwardarr_last_sync_timestamp
A pre-built Grafana dashboard is available at docs/dashboard.json. Import it into your Grafana instance to visualize:
- Application version and build info
- Current forwarded port with change history
- Sync operation success rates and error counts
- Go runtime metrics (memory, goroutines, CPU)
- Time since last successful sync
# Clone the repository
git clone https://github.com/eslutz/forwardarr.git
cd forwardarr
# Build binary
go build -o forwardarr ./cmd/forwardarr
# Build Docker image
docker build -t forwardarr .# Install dependencies
go mod download
# Run tests with race detector and coverage profile (matches CI)
go test -race -coverprofile=coverage.out -covermode=atomic ./...
# View overall coverage
go tool cover -func=coverage.out | tail -1
# View filtered coverage (CI excludes the entrypoint file)
grep -v "cmd/forwardarr/main.go" coverage.out > coverage-filtered.out
go tool cover -func=coverage-filtered.out | tail -1
# Run linter
golangci-lint run
# Run locally
export GLUETUN_PORT_FILE=/path/to/port/file
export TORRENT_CLIENT_URL=http://localhost:8080
export TORRENT_CLIENT_USER=admin
export TORRENT_CLIENT_PASSWORD=adminadmin
go run ./cmd/forwardarrCI enforces ≥60% coverage on the filtered profile (excluding the cmd/forwardarr/main.go entrypoint).
- Verify qBittorrent is accessible at the configured address
- Check credentials are correct
- Ensure network connectivity between containers
- Check logs:
docker logs forwardarr
- Verify Gluetun is writing to the port file:
cat /tmp/gluetun/forwarded_port - Check the port file path is correct in Forwardarr config
- Ensure the volume mount is working:
docker exec forwardarr cat /tmp/gluetun/forwarded_port - Increase log level to debug:
LOG_LEVEL=debug
- Increase
SYNC_INTERVALto reduce polling frequency - Check for excessive file system events in the watched directory
Contributions are welcome! Please read our Contributing Guidelines before submitting PRs.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Run linters and tests
- Submit a pull request
Please see our Security Policy for reporting vulnerabilities.
This project is licensed under the MIT License - see the LICENSE file for details.
- Gluetun - VPN client with port forwarding
- qBittorrent - BitTorrent client
- fsnotify - Cross-platform file system notifications
- Prometheus - Monitoring system and time series database
- Torarr - Tor proxy container for Prowlarr to access indexers via onion addresses