Skip to content

Databases ​

Nanokit provides native integration with managed and containerized database engines, handling provisioning, connection string injection, branching, and environment-specific overrides.

Supported Engines ​

EngineIDDescription
PostgreSQLpostgresStandard PostgreSQL — managed as a container or pointing to an existing host.
MySQLmysqlStandard MySQL — managed as a container or pointing to an existing host.
MariaDBmariadbCommunity-driven, MySQL-compatible fork — managed as a container (mariadb:11) or existing host. Speaks the MySQL wire protocol (mysql:// URLs, port 3306).
MongoDBmongoDocument database — managed as a container or MongoDB Atlas.
RedisredisIn-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 ​

PropertyTypeDescription
enginestringRequired. Backend engine: postgres, mysql, mariadb, redis, or mongo.
versionstringTarget database version (e.g., "16", "8.0", "7.2"). Defaults to latest stable.
planstringManaged service plan tier (e.g., "free", "pro", "enterprise"). Provider-specific.
storagestringStorage allocation (e.g., "10gb", "100gb"). For managed databases with configurable storage.
envobjectAdditional environment variables injected into the database container.
rootPasswordstringRequired (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.
branchingobjectConfiguration for isolated database copies per environment.

Branching Properties ​

PropertyTypeDefaultDescription
branching.enabledbooleanfalseEnable database branching for non-production environments.
branching.syncDatabooleanfalseWhether to copy data from the parent branch (not just schema).
branching.parentEnvstring"production"The source environment to branch from.

How Branching Works ​

When branching.enabled is true and the target environment is not production:

  1. Environment detection: Nanokit detects the active environment.
  2. Branch Trigger: Branching is explicit. You must provide the --branch <name> flag to nkapp up or nkapp db commands to activate an isolated instance.
  3. 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.
  4. 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
EngineInjected URL shape
postgrespostgres://postgres:<pw>@<host>:5432/<db>
mysql / mariadbmysql://root:<pw>@<host>:3306/<db>
mongomongodb://root:<pw>@<host>:27017/<db>?authSource=admin
redisredis://:<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: myapp

Redis (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_root

MariaDB ​

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: myapp

Managed 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 branching

Password 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:

  1. databases.<name>.rootPassword in nanokit.yml (a literal, or a ${ENV_VAR} / env://VAR / vault://path reference);
  2. otherwise the NANOKIT_DB_ROOT_PASSWORD env 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.

MethodSyntaxUse case
VaultrootPassword: vault://secret/path#keyProduction & shared staging (max security).
Env varrootPassword: "${DB_PASSWORD_MAIN}" → set DB_PASSWORD_MAIN in .envLocal / CI.
DirectrootPassword: "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-password default: set NANOKIT_DB_ROOT_PASSWORD=nanokit-safe-password in your .env so Nanokit can keep connecting, then rotate to a real password with nkapp 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 own

Important.db rotate never 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, explicit nkapp destroy <name> -v (see Brand-new DB below) — rotate never calls it.

What the command does, step by step ​

  1. Resolve the target. Loads nanokit.yml, finds the database <name> and its engine, and computes the running container (nk-<project>-<env>-<name>).

  2. 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).

  3. Update the source first. Writes the new value to the .env variable that backs rootPassword (parsed from its ${VAR} / env://VAR expression, or the DB_PASSWORD_<NAME> convention), so the connection URL Nanokit generates will match the engine.

  4. Apply the rotation, by one of two strategies:

    EngineStrategy
    Postgresin place — ALTER USER postgres WITH PASSWORD '…'
    MongoDBin place — db.changeUserPassword('root', '…')
    MySQL / MariaDBin place — ALTER USER 'root'@'%' IDENTIFIED BY '…'; FLUSH PRIVILEGES;
    Rediscontainer recreate — Redis's password is a --requirepass launch argument, so an in-place CONFIG SET requirepass cannot persist. The container is recreated from config (the same driver path as up), the data volume is preserved, and the rotation is verified: the new password authenticates and the old one is rejected.
  5. Propagate. The database's transitive service dependents are reconciled in-process, with the config re-loaded from disk so the fresh .env password is injected. Pass --no-restart to 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: false

Tip. See Connection Injection above for how the resolved credentials reach your services — via a db:// reference you name yourself, or a <DATABASE_NAME>_URL variable derived from depends_on. Either way you never map credentials by hand.