Skip to content

Port update container for Gluetun to qBittorrent port syncing that updates the listening port on change.

License

Notifications You must be signed in to change notification settings

eslutz/Forwardarr

Forwardarr

Workflow Status Security Check Go Report Card License Release

A lightweight, production-ready Go application that automatically synchronizes port forwarding changes from Gluetun VPN to qBittorrent. Built with observability and reliability in mind.

Features

  • 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

Quick Start

Docker Compose

An example docker-compose.yml file is available at docker-compose.example.yml.

Docker CLI

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:latest

Configuration

All 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)

Architecture

┌─────────┐         ┌──────────┐         ┌────────────┐
│ Gluetun │ writes  │  Port    │ watched │            │
│   VPN   ├────────►│   File   ├────────►│ Forwardarr │
└─────────┘         └──────────┘         └─────┬──────┘
                                                │
                                                │ updates
                                                ▼
                                         ┌─────────────┐
                                         │ qBittorrent │
                                         └─────────────┘

How it works:

  1. Gluetun establishes a VPN connection with port forwarding
  2. Gluetun writes the forwarded port to a file
  3. Forwardarr watches this file for changes using fsnotify
  4. When the port changes, Forwardarr updates qBittorrent's listening port via API
  5. A fallback ticker ensures sync even if file events are missed (configurable, can be disabled)

HTTP Endpoints

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

Endpoint Usage

  • /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.

Prometheus Metrics

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

Example Prometheus Queries

# 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

Grafana Dashboard

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

Building from Source

# 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 .

Development

# 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/forwardarr

CI enforces ≥60% coverage on the filtered profile (excluding the cmd/forwardarr/main.go entrypoint).

Troubleshooting

Forwardarr can't connect to qBittorrent

  • Verify qBittorrent is accessible at the configured address
  • Check credentials are correct
  • Ensure network connectivity between containers
  • Check logs: docker logs forwardarr

Port not updating

  • 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

High resource usage

  • Increase SYNC_INTERVAL to reduce polling frequency
  • Check for excessive file system events in the watched directory

Contributing

Contributions are welcome! Please read our Contributing Guidelines before submitting PRs.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Run linters and tests
  6. Submit a pull request

Security

Please see our Security Policy for reporting vulnerabilities.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Gluetun - VPN client with port forwarding
  • qBittorrent - BitTorrent client
  • fsnotify - Cross-platform file system notifications
  • Prometheus - Monitoring system and time series database

Related Projects

  • Torarr - Tor proxy container for Prowlarr to access indexers via onion addresses

About

Port update container for Gluetun to qBittorrent port syncing that updates the listening port on change.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors 3

  •  
  •  
  •