Self-hosted feature flags and remote configuration for web, mobile, and backend apps.
Nona gives you the same feature flag and remote config capabilities as Firebase Remote Config — without the Google account, without the lock-in, and running entirely on your own infrastructure.
- Toggle feature flags from a dashboard without redeploying
- Update mobile app config (iOS, Android, React Native, Flutter) without an app store release
- Use kill switches to disable broken features in seconds
- Fetch everything via one REST API call — no SDK required in any language
🌐 nonaconfig.com · 🐳 Docker Hub · 📦 npm · 📦 NuGet
- Why We Open Sourced Nona
- Why Nona
- Quick Start
- Repository Layout
- Client Libraries
- API
- Docker Compose
- Migrate from Firebase Remote Config
- Performance
- Architecture
Remote configuration is too often bundled with platform lock-in. We wanted teams to be able to change application behavior quickly without giving up control of their own infrastructure, data ownership, or platform choice.
Nona is our attempt to keep this part of the stack small and understandable: one Docker image, one HTTP API, official clients where they help, and a real migration path away from Firebase Remote Config. The longer story is here: Why we open sourced Nona.
| Nona | Firebase Remote Config | |
|---|---|---|
| Open source | ✅ Apache 2.0 licence | ❌ Closed source |
| Self-hostable | ✅ Docker / Kubernetes | ❌ Google-hosted only |
| No Google account | ✅ | ❌ Required |
| Works without mobile SDK | ✅ Plain HTTP | ❌ Firebase SDK needed |
| .NET / NuGet client | ✅ | ❌ |
| Migration tool | ✅ Built into CLI | — |
| Free forever | ✅ Self-host | Free tier with limits |
Nona runs as a single Docker container with an embedded libSQL database — no external database, no separate control plane, no cloud dependency.
docker run -d \
--name nona \
--restart unless-stopped \
-p 18080:8080 \
-v nona-data:/var/lib/nona \
rywaredev/nona:latest- Web UI:
http://localhost:18080 - API base:
http://localhost:18080
Create a project, add environments, and set your first key-value pair. Then fetch your config:
curl http://localhost:18080/v1/config/my-app/production \
-H "X-API-Key: your-api-key"{
"checkout_v2": true,
"dark_mode": false,
"banner_text": "Hello",
"max_upload_mb": 50
}This repository is the Nona monorepo:
core,cli,libsql,migrator: backend API, CLI, storage library, and migration toolingadmin: admin web UIclient: JavaScript SDK, .NET SDK, and JavaScript OpenFeature providerdocs: documentation site
npm install nona-clientimport { createNonaClient } from "nona-client";
const nona = createNonaClient({
baseUrl: "https://nona.example.com",
environmentId: "production",
apiKey: process.env.NONA_API_KEY
});
const value = await nona.getConfigValue("Features:Checkout");
console.log(value.value);📦 npmjs.com/package/nona-client
dotnet add package Nona.Clientusing Nona.Client;
var client = new NonaClient("https://nona.example.com", "production", apiKey: "your-api-key");
var value = await client.GetConfigValueAsync("Features:Checkout");
Console.WriteLine(value.Value);📦 nuget.org/packages/Nona.Client
npm install nona-client nona-openfeature-provider @openfeature/server-sdkSee client/javascript-openfeature-provider/README.md for setup and usage.
No SDK needed. A single GET request returns all config for a project and environment as JSON:
# curl
curl https://your-nona-host/v1/config/{project}/{environment} \
-H "X-API-Key: your-api-key"
# Python
import httpx
config = httpx.get(
"https://your-nona-host/v1/config/my-app/production",
headers={"X-API-Key": api_key}
).json()
# Go
req, _ := http.NewRequest("GET", "https://your-nona-host/v1/config/my-app/production", nil)
req.Header.Set("X-API-Key", apiKey)# Windows via Chocolatey
choco install nona-cliOr download the binary from GitHub Releases.
🍫 community.chocolatey.org/packages/nona-cli
| Method | Path | Description |
|---|---|---|
GET |
/v1/config/{project}/{environment} |
Fetch all config for a project/environment |
GET |
/v1/config/{project}/{environment}/{key} |
Fetch a single key |
Authentication: X-API-Key request header.
Copy deploy/compose/standalone-prod.yml to your server:
docker compose -f standalone-prod.yml up -dDefault host port: http://localhost:18080
| Variable | Default | Description |
|---|---|---|
NONA_API_PORT |
18080 |
Host port mapped to the API |
Jwt__Key |
auto-generated | JWT signing key |
Jwt__Issuer |
nona |
JWT issuer claim |
Jwt__Audience |
nona |
JWT audience claim |
For read-heavy workloads or geographically distributed deployments, use deploy/compose/primary-replica-prod.yml:
docker compose -f primary-replica-prod.yml up -d| Service | API port | libSQL port | gRPC port |
|---|---|---|---|
nona-primary |
18081 |
19080 |
15001 |
nona-replica |
18082 |
19082 |
— |
The replica connects to the primary over gRPC and syncs automatically. Reads served from the replica are 10–15× faster for remote clients (see Performance).
Nona auto-generates JWT settings on first start. To pin your own values:
docker run -d \
--name nona \
-p 18080:8080 \
-v nona-data:/var/lib/nona \
-e Jwt__Key=<your-secret-key> \
-e Jwt__Issuer=nona \
-e Jwt__Audience=nona \
rywaredev/nona:latestThe Nona CLI includes a built-in Firebase Remote Config migration command that imports your existing parameters using a migration config file.
# Install CLI
choco install nona-cli
# Run migration
nona migrate firebase \
--config ./nona.migration.json \
--base-url http://localhost:18080See cli/src/Nona.Cli/README.md for the full CLI reference.
All measurements from production-equivalent environments.
Dataset: 10,000 rows, p95 latency:
| Scenario | p95 (ms) | req/s |
|---|---|---|
| Point lookup — 1 key | 0.154 | 11,248 |
| Point lookup — 100 keys | 0.712 | 1,616 |
| Range query — 1,000 rows | 3.703 | 289 |
All targets pass with 0% error rate under load.
| Scenario | Primary p95 | Replica p95 |
|---|---|---|
| 1 key, c1 | 50.0 ms | 2.6 ms |
| 100 keys, c1 | 56.8 ms | 6.6 ms |
| 1,000 rows, c1 | 263.2 ms | 42.9 ms |
Replica reads are 10–15× faster for geographically distant clients.
Note: Replication is asynchronous. Immediate read-after-write consistency is not guaranteed — replicas are best for read-heavy workloads where eventual consistency is acceptable.
┌─────────────────────────────────────┐
│ Nona Container (rywaredev/nona) │
│ │
│ ┌──────────┐ ┌─────────────────┐ │
│ │ Web UI │ │ HTTP API │ │
│ │ :8080 │ │ :8080 │ │
│ └──────────┘ └─────────┬───────┘ │
│ │ │
│ ┌────────▼────────┐│
│ │ embedded libSQL││
│ │ /var/lib/nona ││
│ └─────────────────┘│
└─────────────────────────────────────┘
- No external database — libSQL is bundled in the container image
- Single port — API and Web UI share port 8080
- Persistent volume — mount
/var/lib/nonato survive container restarts - Optional replica — add a read replica with the primary/replica compose file
Issues and pull requests are welcome. See the issues tracker to report bugs or request features.
Apache 2.0 — free to use, self-host, and modify.
Built by Ryware.dev