Appearance
Databases
Nanokit provides native integration with managed and containerized database engines, handling provisioning, connection string injection, branching, and environment-specific overrides.
Supported Engines
| Engine | ID | Description |
|---|---|---|
| PostgreSQL | postgres | Standard PostgreSQL — managed as a container or pointing to an existing host. |
| MySQL | mysql | Standard MySQL — managed as a container or pointing to an existing host. |
| MariaDB | mariadb | Community-driven, MySQL-compatible fork — managed as a container (mariadb:11) or existing host. Speaks the MySQL wire protocol (mysql:// URLs, port 3306). |
| MongoDB | mongo | Document database — managed as a container or MongoDB Atlas. |
| Redis | redis | In-memory key-value store — managed as a container or managed Redis. |
These five IDs are the only values engine: accepts. Note the ID is mongo, not mongodb.
Planned (roadmap.v3): managed Neon (serverless PostgreSQL) and Turso (edge LibSQL) are not currently selectable engines — they are deferred pending real-account validation. The near-term, in-philosophy target (roadmap.v2) is a self-hosted libSQL (
sqld) container engine; the managed cloud paths are roadmap.v3. See Providers → Roadmap.
Property Reference
| Property | Type | Description |
|---|---|---|
engine | string | Required. Backend engine: postgres, mysql, mariadb, redis, or mongo. |
version | string | Target database version (e.g., "16", "8.0", "7.2"). Defaults to latest stable. |
plan | string | Managed service plan tier (e.g., "free", "pro", "enterprise"). Provider-specific. |
storage | string | Storage allocation (e.g., "10gb", "100gb"). For managed databases with configurable storage. |
env | object | Additional environment variables injected into the database container. |
rootPassword | string | Required (for password-protected engines). Root/Admin password for the database. There is no default — reference an env var (e.g. "${DB_PASSWORD_MAIN}", value in your git-ignored .env) or a vault:// path. |
branching | object | Configuration for isolated database copies per environment. |
Branching Properties
| Property | Type | Default | Description |
|---|---|---|---|
branching.enabled | boolean | false | Enable database branching for non-production environments. |
branching.syncData | boolean | false | Whether to copy data from the parent branch (not just schema). |
branching.parentEnv | string | "production" | The source environment to branch from. |
How Branching Works
When branching.enabled is true and the target environment is not production:
- Environment detection: Nanokit detects the active environment.
- Branch Trigger: Branching is explicit. You must provide the
--branch <name>flag tonkapp upornkapp dbcommands to activate an isolated instance. - Branch creation:
- Local Docker (Mongo/Postgres/Redis):
- Performs an Atomic Volume Clone (
cp -af) from the parent environment's volume. - Creates a uniquely named Docker volume (e.g.,
nk-mongo-data-local_db-feat-branch). - Orchestrates a dedicated database container using the cloned volume, ensuring sub-second startup with a complete data snapshot.
- Performs an Atomic Volume Clone (
- Local Docker (Mongo/Postgres/Redis):
- Connection injection: The connection string for the new branch is automatically injected into application services.
Connection Injection
Nanokit never asks you to copy a password between blocks. There are two injection mechanisms, and both derive the URL from the same resolved rootPassword.
1. db:// references (explicit, any variable name)
Write a db://<database> value anywhere in a service's env: block. At reconciliation time the URI is replaced with a real connection string, under whatever key you chose:
yaml
services:
myapp:
env:
DATABASE_URL: db://main_db # the key is yours; db:// is the trigger- In production: resolves to the production database host.
- On a branch: resolves to the branch-specific instance (e.g.
postgres://postgres:…@nk-project-env-main_db-feat-x:5432/main_db).
This avoids hardcoding branch names and keeps code-to-database parity during development.
2. depends_on (transparent, derived variable name)
A service that lists a database in depends_on additionally receives a <DATABASE_NAME>_URL variable, uppercased with non-alphanumerics replaced by underscores:
yaml
services:
api:
depends_on: [main_db] # → MAIN_DB_URL is injected automatically| Engine | Injected URL shape |
|---|---|
postgres | postgres://postgres:<pw>@<host>:5432/<db> |
mysql / mariadb | mysql://root:<pw>@<host>:3306/<db> |
mongo | mongodb://root:<pw>@<host>:27017/<db>?authSource=admin |
redis | redis://:<pw>@<host>:6379 |
For cloud/managed databases (mode: cloud on a non-local environment), credentials Nanokit does not own are read from Vault (vault://secret/nanokit/<project>/<env>/databases/<db>), falling back to a NANOKIT_DB_<NAME> env reference. If neither resolves, Nanokit prints an explicit warning rather than booting the service silently without credentials.
Zero-Copy Strategy
Nanokit implements a Zero-Copy strategy for branching to ensure that developers have immediate access to production-like data without the wait of traditional dumps or clones.
Local (Logical Overlay)
For local development, Nanokit uses high-speed volume cloning. By leveraging an alpine helper container, Nanokit performs a recursive copy of the database data directory at the file system level. This is significantly faster than SQL dumps and ensures that every branch is completely isolated.
Cloud (Native) — planned (roadmap.v3)
Managed cloud providers (Neon Branching, Turso Database Forking) would create sub-second, zero-storage-cost snapshots via native platform APIs. This managed path is parked pending roadmap.v3 real-account validation; today, branching uses the local volume-clone strategy above.
Examples
PostgreSQL (Standard)
yaml
databases:
main_db:
engine: postgres
version: "16"MongoDB
yaml
databases:
documents:
engine: mongo
version: "7.0"
storage: "10gb"
env:
MONGO_INITDB_DATABASE: myappRedis (Cache)
yaml
databases:
cache:
engine: redis
version: "7.2"MySQL
yaml
databases:
app_db:
engine: mysql
version: "8.0"
env:
MYSQL_DATABASE: myapp
MYSQL_ROOT_PASSWORD: vault://secret/data/mysql_rootMariaDB
A MySQL-compatible fork. It reuses the MySQL machinery end-to-end — same MYSQL_* env, port 3306, mysql:// connection URLs, in-place rotation and mysqldump backups — only the container image differs (mariadb:11 by default, overridable via image / version).
yaml
databases:
app_db:
engine: mariadb
version: "11" # → mariadb:11 (or set `image:` for a full override)
rootPassword: "${DB_PASSWORD_APP}" # value in your git-ignored .env
env:
MYSQL_DATABASE: myappManaged Neon / Turso — planned (roadmap.v3)
Managed Neon (serverless PostgreSQL, native zero-copy branching) and Turso (edge LibSQL, native forking) are not selectable today. Their managed-API integration exists but is parked pending real-account validation (roadmap.v3). The near-term, in-philosophy target is instead a self-hosted libSQL (sqld) container engine (roadmap.v2). See Providers → Roadmap.
AWS RDS
yaml
databases:
production_db:
engine: postgres
version: "16"
plan: db.t3.medium
storage: "100gb"
branching:
enabled: false # Standard RDS does not support zero-copy branchingPassword Rotation (Best Practice)
yaml
databases:
secure_db:
engine: postgres
# Method 1: Using Vault (Production)
rootPassword: vault://secret/db_root_password
# Method 2: Using Environment Variable (Staging/Local)
# rootPassword: ${DB_ROOT_PASSWORD}Passwords & rotation
How the password is resolved
Database passwords are not auto-generated — they are resolved deterministically from a single source, in this order:
databases.<name>.rootPasswordinnanokit.yml(a literal, or a${ENV_VAR}/env://VAR/vault://pathreference);- otherwise the
NANOKIT_DB_ROOT_PASSWORDenv var (global fallback).
There is no built-in default password: if neither source resolves, Nanokit fails fast with a clear error instead of silently using a well-known value.
That single value is both applied to the container at creation (POSTGRES_PASSWORD / MONGO_INITDB_ROOT_PASSWORD / …) and baked into the connection URL injected into dependent services. So you only ever set the source — you never copy a generated password into other services by hand.
| Method | Syntax | Use case |
|---|---|---|
| Vault | rootPassword: vault://secret/path#key | Production & shared staging (max security). |
| Env var | rootPassword: "${DB_PASSWORD_MAIN}" → set DB_PASSWORD_MAIN in .env | Local / CI. |
| Direct | rootPassword: "my-complex-pass" | Simple local testing (don't commit secrets). |
Important. Never put an inline fallback in the reference (
${VAR:-somepass}commits a weak secret to git) — keep the value only in the git-ignored.env.Legacy databases created under the old
nanokit-safe-passworddefault: setNANOKIT_DB_ROOT_PASSWORD=nanokit-safe-passwordin your.envso Nanokit can keep connecting, then rotate to a real password withnkapp db rotate.
Rotating an existing password
Database engines only read the init password when the data volume is first created — so changing the source alone does not rotate an existing DB. The nkapp db rotate command does it safely, without data loss:
bash
nkapp db rotate <name> -e <env> # generate a strong password
nkapp db rotate <name> --password '<value>' -e <env> # or supply your ownImportant.
db rotatenever destroys data — it is safe to run on a live database. It never touches or deletes the data volume. The only command that wipes data is the separate, explicitnkapp destroy <name> -v(see Brand-new DB below) —rotatenever calls it.
What the command does, step by step
Resolve the target. Loads
nanokit.yml, finds the database<name>and its engine, and computes the running container (nk-<project>-<env>-<name>).Determine the passwords. Reads the current root password from the source (so it can authenticate) and the new one (your
--password, or a freshly generated 32-char value).Update the source first. Writes the new value to the
.envvariable that backsrootPassword(parsed from its${VAR}/env://VARexpression, or theDB_PASSWORD_<NAME>convention), so the connection URL Nanokit generates will match the engine.Apply the rotation, by one of two strategies:
Engine Strategy Postgres in place — ALTER USER postgres WITH PASSWORD '…'MongoDB in place — db.changeUserPassword('root', '…')MySQL / MariaDB in place — ALTER USER 'root'@'%' IDENTIFIED BY '…'; FLUSH PRIVILEGES;Redis container recreate — Redis's password is a --requirepasslaunch argument, so an in-placeCONFIG SET requirepasscannot persist. The container is recreated from config (the same driver path asup), the data volume is preserved, and the rotation is verified: the new password authenticates and the old one is rejected.Propagate. The database's transitive service dependents are reconciled in-process, with the config re-loaded from disk so the fresh
.envpassword is injected. Pass--no-restartto skip this and get the manual hint instead.
A failed propagation never masks a successful rotation
If the dependent reconcile fails it is reported separately and the command exits non-zero — but the password was rotated. Re-run nkapp up -e <env> to finish propagating it.
Brand-new DB (or disposable data)
If the database has no data you need to keep, you don't need rotate — just set the source and recreate the volume so it re-initializes with the new password:
bash
nkapp destroy <name> -v -e <env> # -v removes the data volume
nkapp up -e <env>Note. Managed/cloud engines (e.g. Neon — planned, roadmap.v3) would be rotated through the provider/Vault and a redeploy, not by
db rotate.
Per-Environment Database Overrides
Databases follow the same Deep Merge strategy as the rest of nanokit.yml:
yaml
databases:
main_db:
engine: postgres
branching:
enabled: true
environments:
local:
databases:
main_db:
engine: postgres # Use standard Postgres container for local dev
version: "16"
branching:
enabled: falseTip. See Connection Injection above for how the resolved credentials reach your services — via a
db://reference you name yourself, or a<DATABASE_NAME>_URLvariable derived fromdepends_on. Either way you never map credentials by hand.