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
A real Nona instance with sample projects, environments and releases. You are signed in automatically as an administrator — no account, no install. Everything resets nightly, so change whatever you like.
🌐 nonaconfig.com · 🐳 Docker Hub · 📦 npm · 📦 NuGet
- Why We Open Sourced Nona
- Why Nona
- Quick Start
- Repository Layout
- Client Libraries
- API
- Docker Compose
- Migrate existing configuration
- 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 SQLite in standalone mode and embedded libSQL when primary/replica replication is configured — no external database, separate control plane, or cloud dependency.
Want to look around before installing anything? Try the live demo.
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 - Guided setup:
https://nonaconfig.com/docs/get-started/
Create a project, add an environment, set your first key-value pair, and create an API key. Then fetch the working value:
curl "http://localhost:18080/api/environments/production/parameters/Features%3ACheckout" \
-H "X-Api-Key: your-api-key"HTTP/1.1 200 OK
X-Nona-Content-Type: boolean
trueThe API key is bound to one project, so the request path only needs the environment and key. For a full walkthrough, start with First project and First API call.
If you are expecting LaunchDarkly-style evaluation, this is the key distinction: Nona reads are keyed by project, environment, scope, and key. There is no built-in runtime targeting, percentage rollout, or userId-based evaluation on the HTTP read path.
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, Kotlin/Android SDK, Swift SDK, and the OpenFeature providersdocs: 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,
useReleases: true,
releaseVersion: "1.1.x"
});
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
The Swift client supports CocoaPods and Swift Package Manager, with defaults, offline caching, and explicit fetch/activate. See client/swift/README.md for branch installation and usage.
val config = NonaConfig.create(context, NonaOptions(
baseUrl = "https://nona.example.com",
environmentId = "production",
apiKey = BuildConfig.NONA_FRONTEND_KEY
))
config.setDefaults(mapOf("Features:Checkout" to false))
config.initialize() // restore the cached snapshot
config.fetchAndActivate() // refresh from the network
val enabled = config.getBoolean("Features:Checkout")In-app defaults, separate fetch and activate, synchronous reads, and an offline cache that survives restarts. Usable from Java too, and needs a frontend-scoped API key. See client/kotlin/README.md.
npm install nona-client nona-openfeature-provider @openfeature/server-sdkSee client/javascript-openfeature-provider/README.md for setup and usage.
npm install nona-client nona-openfeature-web-provider @openfeature/web-sdkLoads the environment's frontend-scoped config as one snapshot and evaluates synchronously, which is what the OpenFeature web SDK expects. Requires a frontend-scoped API key. See client/javascript-openfeature-web-provider/README.md for setup and usage.
No SDK needed. Choose the working, active-release, or selected-release route explicitly. The version is a path segment and may be exact or a release line:
# curl
curl "https://your-nona-host/api/environments/production/releases/1.1.x/parameters/Features%3ACheckout" \
-H "X-Api-Key: your-api-key"
# Python
import httpx
value = httpx.get(
"https://your-nona-host/api/environments/production/parameters/Features%3ACheckout",
headers={"X-Api-Key": api_key}
).text
# Go
req, _ := http.NewRequest("GET", "https://your-nona-host/api/environments/production/parameters/Features%3ACheckout", nil)
req.Header.Set("X-Api-Key", apiKey)The response body is the stored value. Nona also returns the logical type in the X-Nona-Content-Type response header.
# npm
npm install -g nona-cli# Windows via Chocolatey
choco install nona-cliOr download the binary from GitHub Releases.
CLI packages:
| Method | Path | Description |
|---|---|---|
GET |
/api/environments/{environmentId}/parameters/{key} |
Fetch one working parameter |
GET |
/api/environments/{environmentId}/parameters |
Fetch all client-visible working parameters with ETag support |
GET |
/api/environments/{environmentId}/releases/active/parameters/{key} |
Fetch one parameter from the active release |
GET |
/api/environments/{environmentId}/releases/{version}/parameters/{key} |
Fetch one parameter from an exact or wildcard release selector |
GET |
/api/environments/{environmentId}/releases/active/parameters |
Fetch all client-visible parameters from the active release |
GET |
/api/environments/{environmentId}/releases/{version}/parameters?prefix=GroupA%3A |
Fetch a prefix from an exact or wildcard release selector |
Authentication: X-Api-Key request header.
Non-empty prefixes may contain only ASCII letters, digits, colons, dots, underscores, and dashes. Invalid prefixes return 400 Bad Request; an empty prefix is unfiltered.
The API key determines the project. The response body contains the raw stored value, and X-Nona-Content-Type tells the client whether the value is text, number, boolean, or json.
The API does not accept per-user evaluation context for runtime flag resolution. Query parameters or headers such as userId or X-User-Id are not part of the Nona read model.
See HTTP client docs for examples and troubleshooting.
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 |
internal only | internal only |
nona-replica |
18082 |
internal only | — |
The replication compose files publish only the Nona API. SQL HTTP and replication gRPC stay on the private container network; Nona authentication does not protect these database listeners.
The replica connects to the primary over gRPC and syncs automatically.
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.
The CLI can also import AWS Parameter Store String values referenced by an ECS task definition:
nona migrate parameter-store \
--task-definition ./task-definition.json \
--environment production \
--project backend-service \
--dry-runThe following results measure full runtime API reads using SQLite. "Users" means concurrent, closed-loop HTTP clients.
Each request used GET /api/environments/{environment}/parameters and consumed the complete response
body.
| Keys returned | Users | Average (ms) | p50 (ms) | p95 (ms) | p99 (ms) | req/s |
|---|---|---|---|---|---|---|
| 1 | 1 | 0.731 | 0.725 | 0.812 | 0.930 | 1,361.6 |
| 1 | 50 | 5.950 | 5.658 | 8.763 | 11.522 | 8,397.7 |
| 100 | 1 | 1.351 | 1.314 | 1.537 | 2.043 | 738.6 |
| 100 | 50 | 7.723 | 6.812 | 12.451 | 23.230 | 6,469.5 |
Each request used GET /api/environments/{environment}/parameters/{key} to read one fixed key from an
environment containing 10,000 keys.
| Keys returned | Users | Average (ms) | p50 (ms) | p95 (ms) | p99 (ms) | req/s |
|---|---|---|---|---|---|---|
| 1 | 1 | 0.664 | 0.644 | 0.749 | 1.410 | 1,501.2 |
| 1 | 50 | 5.250 | 4.900 | 7.969 | 13.302 | 9,516.8 |
| 1 | 100 | 10.189 | 9.884 | 15.238 | 21.706 | 9,804.9 |
┌─────────────────────────────────────┐
│ Nona Container (rywaredev/nona) │
│ │
│ ┌──────────┐ ┌─────────────────┐ │
│ │ Web UI │ │ HTTP API │ │
│ │ :8080 │ │ :8080 │ │
│ └──────────┘ └─────────┬───────┘ │
│ │ │
│ ┌────────▼────────┐│
│ │ SQLite / libSQL ││
│ │ /var/lib/nona ││
│ └─────────────────┘│
└─────────────────────────────────────┘
- No external database — standalone uses SQLite; sqld is bundled for primary/replica mode
- 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