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.