Distributed Deployment
Bottom line first: standalone RuleGo-Server (file storage) is designed as a single instance — rule chains, users, settings and run logs all live in the instance's own data directory and are invisible to other replicas; a chain created via the API on replica A does not exist on replica B. Real distributed deployment is not an INI switch in the server — the embedding host provides the shared storage and the distributed lock.
Two verified paths follow; pick one for your scenario.
# Path 1 (recommended): distributed via an embedding host
For: platforms that need multi-replica load balancing, centralized data and unified authentication. Reference deployment: the GFlow platform (opens new window) (two active-active replicas on PG/MySQL), whose approval engine GFlow Engine (opens new window) is open source.
The host embeds rulego-server as a Go library and wires distributed capability in three steps:
# Step 1: shared storage (StoreProvider SPI)
Implement the StoreProvider interface from server/store (six stores: rule chains, settings, components, node pools, users, run logs) on top of your platform's relational database:
provider := NewMyDBStoreProvider(db) // gorm/ent/sqlx all work
app.New(
app.WithConfig(rgCfg),
app.WithStoreProvider(provider),
...
)
2
3
4
5
6
With data in the shared database, every replica reads and writes the same rule chains — consistent by construction.
# Step 2: inject the distributed lock (Config.Locker)
Implement the types.Locker interface (Redis is the usual choice) and inject it into the rulego config:
rgCfg.Locker = myRedisLocker // type: types.Locker
Every user engine then gains two cross-replica semantics automatically (see At-Most-Once Execution for the mechanics):
- Schedule endpoint dedup: a scheduled slot fires exactly once per cluster
- Single-active broadcast endpoints: Redis Pub/Sub, RabbitMQ and MySQL CDC endpoints are consumed only by the elected leader; a standby takes over automatically when the leader is lost
Lock requirements: keys must expire, Unlock must verify the token (CAS); optionally implement LeaseRenewer for window-free leader renewal. The extension library ships a ready Redis implementation (standalone/sentinel/cluster):
import "github.com/rulego/rulego-components/pkg/locker"
rgCfg.Locker = locker.NewRedisLocker(redisClient)
2
3
# Step 3: single-delivery trigger sources + auth takeover
- Trigger sources with grouping (Kafka consumer groups, MQTT shared subscriptions
$share/g/topic, NATS queue groups) are naturally single-delivery once configured — no lock involved - Take over authentication with
WithAuthenticator/WithAuthorizer(orDisableLocalAuthto drop the local accounts); tenant identity (Owner) partitions the lock keys automatically - Deploy Redis with sentinel/cluster for HA; keep replica clocks NTP-synchronized
For the full wiring contract and multi-tenant details, see the bridge module in the GFlow platform (opens new window) docs.
# Path 2 (limited): read-only identical replicas
For: chains developed offline and baked into the image; replicas only consume, never write (pure acquisition/forwarding edge clusters).
Mount the same read-only data directory on every replica (or bake one data directory into the image), and use grouped consumption on every trigger source (Kafka consumer groups, MQTT shared subscriptions — see the matrix in At-Most-Once Execution).
Know the limits:
- The schedule endpoint (
endpoint/schedule) keeps per-replica clocks and fires on every replica — avoid scheduled chains in this mode, or move to path 1 - Creating chains via the API is unavailable (writes land on a single replica only)
- Broadcast sources such as Redis Pub/Sub cannot be deduplicated without a lock; rely on the source's own grouping
# Deployment checklist (multi-replica)
- [ ] Storage is shared (path 1: StoreProvider; path 2: read-only identical directories)
- [ ] Trigger sources configured for single delivery: Kafka consumer groups, MQTT
$sharesubscriptions, NATS QueueGroup - [ ] Replica clocks are NTP-synchronized
- [ ] Redis is highly available (path 1); election pauses while Redis is down and resumes automatically