Standalone (Bare OpenResty)
You can run Fairvisor Edge without Docker by installing OpenResty directly and loading the Lua modules into your existing OpenResty installation.
Prerequisites
- OpenResty ≥ 1.25.3 (includes LuaJIT 2.1 and the standard nginx Lua modules)
- The Fairvisor Edge Lua sources (from
src/in the release tarball)
Install OpenResty:
# macOS
brew install openresty
# Ubuntu / Debian
apt-get install openresty
# Alpine
apk add openresty
Installation
Copy the Lua sources to a stable path. You need both the core library (src/fairvisor/) and the nginx handler files (src/nginx/):
cp -r /path/to/release/src/ /opt/fairvisor/src/
The resulting layout should be:
/opt/fairvisor/src/
fairvisor/ ← core Lua modules
nginx/ ← per-location handler files
nginx.conf
Minimal nginx.conf for standalone decision-service mode. All handler logic lives in the src/nginx/ files — the nginx config only wires locations to them:
worker_processes auto;
# Expose env vars to Lua (nginx strips them by default)
env FAIRVISOR_CONFIG_FILE;
env FAIRVISOR_SHARED_DICT_SIZE;
env FAIRVISOR_LOG_LEVEL;
env FAIRVISOR_MODE;
events {
worker_connections 1024;
}
error_log /dev/stderr info;
pid /tmp/nginx.pid;
worker_shutdown_timeout 35s;
http {
# Point Lua to the Fairvisor sources
lua_package_path "/opt/fairvisor/src/?.lua;;";
# Shared dict — holds all rate-limit counters
lua_shared_dict fairvisor_counters 128m;
# Load and initialise Fairvisor on each worker start
init_worker_by_lua_file /opt/fairvisor/src/nginx/init_worker.lua;
server {
listen 8080;
# Liveness
location = /livez {
default_type text/plain;
return 200 "ok\n";
}
# Readiness (503 until a policy bundle is loaded)
location = /readyz {
default_type application/json;
content_by_lua_file /opt/fairvisor/src/nginx/readyz.lua;
}
# Prometheus metrics
location = /metrics {
default_type text/plain;
content_by_lua_file /opt/fairvisor/src/nginx/metrics.lua;
}
# Decision endpoint
location = /v1/decision {
default_type application/json;
content_by_lua_file /opt/fairvisor/src/nginx/decision.lua;
}
}
}
Environment setup
Set variables before starting nginx. The init_worker handler reads these at startup:
export FAIRVISOR_CONFIG_FILE=/etc/fairvisor/policy.json
export FAIRVISOR_SHARED_DICT_SIZE=128m
export FAIRVISOR_LOG_LEVEL=info
export FAIRVISOR_MODE=decision_service
Starting and stopping
# Start
openresty -c /etc/fairvisor/nginx.conf
# Test config
openresty -c /etc/fairvisor/nginx.conf -t
# Graceful reload (workers drain in-flight requests)
kill -HUP $(cat /tmp/nginx.pid)
# Stop
openresty -s stop
Hot config reload
To apply a new policy.json without downtime:
cp policy-new.json /etc/fairvisor/policy.json
kill -HUP $(cat /tmp/nginx.pid)
The HUP signal triggers a graceful reload: nginx starts new workers (which re-run init_worker_by_lua_file and load the updated policy), then drains and shuts down the old workers. There is a brief period where both old and new workers are active.
Integrating with an existing nginx
If you already run nginx and want to protect it with Fairvisor, run Edge as a separate process on a different port and use auth_request to call it:
upstream fairvisor_edge {
server 127.0.0.1:8080;
}
server {
location = /_fv_decision {
internal;
proxy_method POST;
proxy_pass http://fairvisor_edge/v1/decision;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header Authorization $http_authorization;
}
location /api/ {
auth_request /_fv_decision;
proxy_pass http://your_backend;
}
}
No Lua required in your nginx — all decision logic runs inside the Edge process.
Limitations
Standalone mode supports decision_service only; reverse_proxy mode requires FAIRVISOR_BACKEND_URL and the full image entrypoint.
There is no SaaS config delivery in standalone mode — you manage policy file updates yourself (see Hot config reload above).
Rate-limit counters live in the Lua shared dict (per-process). A restart resets all counters — active token buckets, cost budgets, and loop counters start fresh.