Flux Shared DB is a solution for persistent, shared database storage on the Flux network. It handles replication between MySQL instances.
Operator nodes discover each other using the FluxOS API and immediately form a cluster. Each Operator node connects to a DB engine through a connection pool. Read queries are proxied directly to the DB engine, while write queries are sent to the master node. The master node timestamps and sequences incoming write queries, then immediately forwards them to slave nodes.

The Operator exposes three interfaces:
To use Flux Shared DB in your project, link it to a DB engine and the Operator handles the rest. A common setup is to run it alongside a DB engine. You can also add your application to the same compose stack and connect directly to the Operator DB port.
mysql.mysql:latest.Commands field: --disable-log-bin.[
"MYSQL_ROOT_PASSWORD=PASSWORD",
"MYSQL_ROOT_HOST=172.0.0.0/255.0.0.0"
]
operator.runonflux/shared-db:latest.s:/app/dumps.Cont. Ports field: [3307,7071,8008].Ports field, map those ports to new ones, for example: [13307,17071,18008].Domains field, add this: ["","",""].[
"DB_COMPONENT_NAME=mysql",
"INIT_DB_NAME=my-db",
"DB_INIT_PASS=PASSWORD",
"OPERATOR_API_SECRET=REPLACE_WITH_RANDOM_64_HEX_SECRET",
"DB_USER=root",
"DB_PORT=13307",
"API_PORT=17071"
]
"server=operator:3307;uid=DB_USER;pwd=DB_INIT_PASS;database=INIT_DB_NAME"
Three image variants are published to Docker Hub:
| Image | Contents | Tags | DB component needed? |
|---|---|---|---|
runonflux/shared-db:latest / :dev | Distroless operator only (DB-less) | latest (master), dev (development) | Yes - a separate DB component |
runonflux/shared-db:latest-mysql / :dev-mysql | Operator + MySQL 8 bundled | latest-mysql, dev-mysql | No - DB runs inside the container |
runonflux/shared-db:latest-mariadb / :dev-mariadb | Operator + MariaDB bundled | latest-mariadb, dev-mariadb | No - DB runs inside the container |
dev* tags are built from the development branch, latest* from master.
Pick the DB-less image if you want to run and manage the DB engine as a separate component (setup above). Pick a bundled image for a single self-contained component (setup below). Do not use both approaches for the same deployment.
The bundled images run the DB engine (MySQL 8 or MariaDB) inside the same container as the Operator, so you only register a single component.
operator.runonflux/shared-db:latest-mysql (or runonflux/shared-db:latest-mariadb).DB_COMPONENT_NAME (it defaults to localhost, connecting to the in-container DB).s:/app/dumps|/app/db.
s:/app/dumps holds SQL backups and is synced across instances (the s: flag)./app/db is the DB engine data directory. It must be persistent but node-local (no s: flag) - each node keeps its own copy and replication is handled by the Operator.Cont. Ports field: [3307,7071,8008].Ports field, map those ports to new ones, for example: [13307,17071,18008].Domains field, add this: ["","",""].[
"INIT_DB_NAME=my-db",
"DB_INIT_PASS=PASSWORD",
"OPERATOR_API_SECRET=REPLACE_WITH_RANDOM_64_HEX_SECRET",
"DB_USER=root",
"DB_PORT=13307",
"API_PORT=17071"
]
Note: DB_INIT_PASS sets the DB root password on first boot only. Changing it later will not re-key an already-initialized /app/db datadir.
Missing or empty DB_USER and DB_INIT_PASS retain the historical defaults root and secret; existing deployments using those credentials continue working without new environment variables. Blank client passwords are still rejected.
The MySQL emulator requires the configured DB_USER and the effective DB password (initialized from DB_INIT_PASS), in addition to its existing IP and cluster-readiness checks. Whitelisted and application IPs must authenticate too. It verifies the mysql_native_password challenge response against the fresh per-connection salt before allocating a pooled DB connection. Clients that initially select a different plugin (including MySQL 8 clients starting with caching_sha2_password) receive a native-password auth-switch request with a fresh challenge. The switched response is verified before username authorization or pool allocation. Wrong or omitted passwords, incorrect usernames, malformed handshakes, and unsupported authentication protocols receive error 1045 / SQLSTATE 28000 and the connection closes. A client that closes during authorization or pool allocation cannot retain a pooled connection.
Automatic local-network access is limited to valid RFC1918 IPv4 addresses: 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16. Public addresses (including other 172.x ranges) require an explicit WHITELIST entry or a discovered application IP match. Local access still requires valid credentials and follows AUTH_MASTER_ONLY; loopback access uses the default 127.0.0.1 whitelist entry.
The master requires an admitted replication peer before accepting DB clients or treating its incoming peer connection as healthy. Raw Engine.IO sessions, rejected proofs, and new clients explicitly declaring a discovery role do not satisfy this check. New persistent peers identify their replica role, while new discovery clients explicitly declare a discovery role. Older peers without a role field remain eligible once admitted, including while importing an initial snapshot before any backlog request. Proof-bearing older peers must authenticate; unauthenticated legacy peers count only during the grace period.
This interface exposes a single configured account backed by the existing privileged connection pool; it does not implement separate MySQL user accounts or per-user grants. The operator API secret is separate and is not a MySQL login password. The emulator continues to use its existing native-password wire protocol without MySQL TLS support.
Regression coverage includes malformed and replayed handshakes, commands pipelined after failed authentication, and username rejection on trusted IPs. The three-node integration test also checks correct, incorrect, and missing credentials with real mysql2 and MariaDB clients, then verifies authenticated replication.
Updated operator clients always send a session-bound HMAC proof. This release accepts legacy connections without proofs from allowed operator IPs until 2026-10-25 at 17:48:17 UTC, then requires authentication for discovery, election, replication, key exchange, and session synchronization. This is a fixed release cutoff, not 48 hours after each restart. Invalid proofs and unauthorized IPs are rejected even during the grace period. The previous shared-IP exposure remains during that period.
At the cutoff, already-connected unauthenticated peers are disconnected, including passive broadcast subscribers; incoming legacy commands are also blocked at dispatch. Authenticated connections remain open. Nodes starting after the cutoff enforce authentication immediately. Once enforcement is observed, a running process never reopens legacy access because of a backwards clock adjustment. Startup logs the cutoff, and getStatus reports operatorAuth.version, operatorAuth.required, operatorAuth.enforceAt, and operatorAuth.secondaryKeyConfigured.
To override the release cutoff, set the same OPERATOR_AUTH_ENFORCE_AT on every replica using an ISO UTC timestamp such as 2026-10-25T17:48:17Z. A past timestamp enforces immediately; invalid or empty values prevent startup. Keep node clocks synchronized. Old binaries cannot enforce the cutoff themselves, so complete their replacement before it. TLS remains a separate setting; keep the transport consistent during the mixed-version rollout.
Set the same randomly generated OPERATOR_API_SECRET on every replica (at least 32 bytes; for example, generate one with openssl rand -hex 32). If it is unset, the operator derives its authentication key from the effective DB password. For backward compatibility, the historical default secret and existing short DB passwords remain accepted, and startup logs a warning for passwords shorter than 32 bytes. The public default produces a predictable API key and does not protect against another app on an allowed node; configure a private password or dedicated API secret for that protection. An explicitly empty OPERATOR_API_SECRET is rejected rather than falling back to the DB password.
The key is derived with HKDF-SHA256 using DB_APPNAME and a versioned operator authentication purpose. DB_APPNAME is resolved from Flux hostinfo when unset; set it explicitly for local deployments. All replicas must use the same app name. Neither the password nor the derived key is transmitted: the proof authenticates the fresh Engine.IO session ID, so a captured proof cannot be reused on a later connection.
For encryption and protection against active network interception, configure OPERATOR_TLS_CERT and OPERATOR_TLS_KEY with PEM file paths on every replica. Clients then use HTTPS and verify server certificates. For a private CA, also set OPERATOR_TLS_CA to its PEM file. Certificates must cover the peer IPs, or set OPERATOR_TLS_SERVERNAME to a shared DNS identity covered by the certificates. The existing SSL option controls the UI separately. Key derivation, certificate/CA reads, and client HTTPS agents are cached lazily after the cluster identity is resolved. Restart instances to load changed TLS files. Without operator TLS, traffic remains readable and susceptible to active relaying/tampering despite replay-resistant connection authentication.
Complete the rolling upgrade before the cutoff: old clients can communicate with updated servers during the grace period, and updated clients can communicate with old servers. After the cutoff, updated servers reject old clients. This grace period does not relax the separate MySQL username/password or private-network checks. Rotating a secret directly during a rolling update is not safe: the grace period accepts missing legacy proofs, never incorrect proofs. Use the staged rotation below for an independent API secret. When using the DB password fallback, changing DB_INIT_PASS also changes API authentication, but does not change an existing DB engine password; coordinate the actual DB password change separately. A password-derived API key also gives DB password holders operator privileges; use the dedicated secret when those permissions should differ.
OPERATOR_API_SECRET_SECONDARY is an optional acceptance-only key. Clients always send proofs using the primary OPERATOR_API_SECRET (or the DB password fallback); servers accept either explicitly configured key. No secrets or additional proofs are sent to older peers.
OPERATOR_API_SECRET_SECONDARY=B on every replica. Wait until every instance is updated and healthy. All peers still send A, so peers that have not yet updated can communicate.When migrating from the DB-password fallback, keep DB_INIT_PASS unchanged and use its current value as A in the switch phase. A retiring legacy DB password may be shorter than 32 bytes; an explicitly empty secondary is rejected. A new dedicated primary still requires at least 32 bytes. Old keys remain valid until retired. Check getStatus for secondaryKeyConfigured and rollout health before switching; do not skip preparation or silently fall back after failed authentication.
| Variable | Description | Default |
|---|---|---|
DB_COMPONENT_NAME | Hostname for the DB engine component. Required for the DB-less image; leave unset for bundled images (defaults to localhost). | localhost |
INIT_DB_NAME | Initial database name created immediately after initialization | Required |
DB_INIT_PASS | Root password for the DB engine | secret |
OPERATOR_AUTH_ENFORCE_AT | Shared UTC cutoff for requiring operator authentication; ISO datetime ending in Z | 2026-10-25T17:48:17Z |
OPERATOR_API_SECRET_SECONDARY | Optional acceptance-only key for staged rotation; nonempty, may contain the retiring DB password | Unset |
OPERATOR_API_SECRET | Shared random operator authentication secret, minimum 32 bytes | Falls back to DB_INIT_PASS |
DB_APPNAME | Cluster identity for operator key derivation | Resolved from Flux hostinfo |
OPERATOR_TLS_CERT / OPERATOR_TLS_KEY | PEM certificate and private key file paths for the operator API; set both | Unset (HTTP) |
OPERATOR_TLS_CA | PEM CA file for validating operator peer certificates | System trust store |
OPERATOR_TLS_SERVERNAME | Certificate DNS identity when dialing peer IPs | Peer IP |
DB_USER | Username that can authenticate with the Operator | root |
DB_PORT | External DB port for the DB Interface, first item in the Ports array | Required |
API_PORT | External API port for cluster communication, second item in the Ports array | Required |
WHITELIST | Comma-separated list of IPs that can connect remotely to DB_PORT | Optional |
AUTH_MASTER_ONLY | If set to "true", only the master node authenticates DB access from the app. This is useful if you want to keep a single reachable master node and return an error page to FDM so only the master node is reachable to end users. | false |
Content type
Image
Digest
sha256:6dc17e67f…
Size
98.4 MB
Last updated
5 days ago
docker pull runonflux/shared-db