Config and Feature Flags Without a SaaS Service
Ship flags as Postgres rows plus a local cache so you can dark-launch and halt features without a vendor SDK or a round trip on every request.
Netbay Cloud Team
Netbay Engineering
On this page
Feature flags are an operational control plane, not a marketing dashboard. You need them to halt a bad payment path at 02:00, to dark-launch a GST invoice layout for one tenant, and to keep a config value out of a deploy. You do not need a SaaS SDK that phones home, bills per seat, and becomes a single point of failure in front of your own Lucknow origin. A Postgres table, a short TTL cache in each process, and an admin path that only your operators can hit will carry a VPS fleet a long way.
Flags Are Data, Deploys Are Artifacts
A deploy should move code. A flag should move behaviour. If turning off checkout requires a git revert and a systemd restart, you have coupled release risk to product risk. Store flags next to the tenant they apply to. Default deny: a missing flag is off. That way a new node that has not yet synced still fails closed rather than exposing a half-built route.
CREATE TABLE feature_flags (
key text NOT NULL,
tenant_id text NOT NULL DEFAULT '*',
enabled boolean NOT NULL DEFAULT false,
payload jsonb NOT NULL DEFAULT '{}',
updated_at timestamptz NOT NULL DEFAULT now(),
updated_by text NOT NULL,
PRIMARY KEY (key, tenant_id)
);The star tenant_id is the global default. A more specific row wins. payload holds small structured config such as a percentage rollout or a cap. Do not put secrets here. Flags are copied into every web process; secrets belong in files those processes cannot list.
Cache Hard, Invalidate Cheap
Reading Postgres on every HTTP request is how you turn a flags table into a latency budget. Load all flags for the node into memory on boot, then refresh every 15 to 30 seconds. That is an RTO for behaviour change, not for data. If you need a flag to flip in one second, send a reload signal or PUBLISH a tiny Redis message that only means "refetch". Do not stream the flag values through Redis as the source of truth; the table is.
import threading, time, psycopg
class FlagCache:
def __init__(self, dsn, interval=20):
self.dsn = dsn
self.interval = interval
self._flags = {}
self._lock = threading.Lock()
self.refresh()
t = threading.Thread(target=self._loop, daemon=True)
t.start()
def refresh(self):
with psycopg.connect(self.dsn) as conn:
rows = conn.execute(
'SELECT key, tenant_id, enabled, payload FROM feature_flags'
).fetchall()
table = {}
for key, tenant_id, enabled, payload in rows:
table.setdefault(key, {})[tenant_id] = (enabled, payload)
with self._lock:
self._flags = table
def enabled(self, key, tenant_id):
with self._lock:
by_tenant = self._flags.get(key) or {}
row = by_tenant.get(tenant_id) or by_tenant.get('*')
return bool(row and row[0])
def _loop(self):
while True:
time.sleep(self.interval)
try:
self.refresh()
except Exception:
passSwallowing refresh errors is deliberate. A flags database blip must not restart your API. Stale flags for 30 seconds beat a 502. Log the failure and alert if refreshes miss two intervals. The cache is a copy, not a second product.
Who May Flip a Flag
Treat the admin endpoint as production access. mTLS from the jump host, or SSH port-forward to a loopback-only service. Do not put a public "feature console" on the internet with a shared password. Record updated_by. A flag flip is an audit event: who, what, when, previous value. Rollbacks are another UPDATE, not a deploy.
Percentage rollouts belong in payload. Hash tenant_id, take modulo 100, compare to the stored percent. Sticky by tenant, never by request, or a user will flap between layouts. When the percent hits 100, delete the special case and make the code path the default in the next release, then remove the flag. Flags that live forever are undeclared config and they rot.
What Not to Flag
Do not flag schema migrations. A column is there or it is not; a flag cannot hide a missing table. Do not flag security controls. Authz that can be flipped from an admin UI is an incident waiting for a tired operator. Do not flag secrets, hostnames, or database URLs. Those are environment and files, different layer, different rotation story.
Keep the flag catalogue small. If you have 200 keys, you have a configuration product you are not staffing. Review flags monthly. Delete the ones whose code path is now the default. The operational win is a short list you can hold in your head during an incident, not a second CMS.
Workers must see the same table. A billing worker that ignores the halt-checkout flag will keep sending invoices after you thought you stopped the world. Point every role at the same DSN and the same cache interval so a flip is a fleet event, not a web-only event.
Takeaway
Self-hosted flags are a table, a cache, and an audited UPDATE. They let you change behaviour without a deploy and without a SaaS outage in someone else's region. Fail closed, cache on the node, and keep secrets out of the payload.
You can host the flags database on a Netbay Ubuntu VPS in Lucknow (DC01) and point every app node at it without adding a vendor to the request path — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance