# Standalone (Bare OpenResty)

- Canonical URL: https://docs.fairvisor.com/docs/deployment/standalone/
- Section: docs
- Last updated: n/a
> Running Fairvisor Edge directly on a host with OpenResty — no Docker required.


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:

```bash
# 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/`):

```bash
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:

```nginx
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:

```bash
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

```bash
# 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:

```bash
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:

```nginx
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.

<div class="callout callout-note">
  <span class="callout-icon">ℹ️</span>
  <div><p>There is no SaaS config delivery in standalone mode — you manage policy file updates yourself (see <a href="#hot-config-reload">Hot config reload</a> above).</p></div>
</div>

<div class="callout callout-warning">
  <span class="callout-icon">⚠️</span>
  <div><p>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.</p></div>
</div>

