Skip to content

Environments & Overrides ​

Nanokit architecture is based on Environment-First Orchestration. The environments block allows you to override any global setting for specific contexts like local, stage, or production.

Deep Merge Strategy ​

Nanokit uses a Deep Merge logic to resolve the final configuration:

  1. Global Spec: Load all properties from the top-level blocks.
  2. Environment Sync: Merge the matching key from environments (e.g., local).
  3. Conflict Resolution: Specific environment values always overwrite global values.

Service membership — "Exclude by Default" ​

A non-empty services: block REPLACES the root set

Merging applies to a service's properties. Membership works differently:

  • If environments.<env>.services is absent or empty, every root service applies to that environment, each merged with its (possibly absent) override.
  • If environments.<env>.services lists any service at all, then only the listed services are deployed to that environment. Root services you did not list are silently excluded.
  • In that case, only the databases referenced by an included service — via depends_on or a db:// env link — are provisioned. A website-only environment gets no databases.
yaml
services:
  web: { runtime: node, port: 3000 }
  api: { runtime: node, port: 4000 }
  worker: { runtime: node }

environments:
  marketing:
    services:
      web:                 # ← only `web` deploys to `marketing`.
        host: example.com  #   `api` and `worker` are excluded, not merged.

This is the single most common source of "why isn't my service deploying?". If you want every root service plus an override, list all of them, or move the override into the root block.


Overridable Blocks ​

Almost everything in the global config can be overridden per environment:

BlockExample Override
infraChange provider from aws to docker for local dev
runtimesChange global images or env vars per environment
servicesChange port, host, commands, env vars per environment (see membership rule above)
databasesChange engine, version or branching per environment
deploySet different SSH targets per environment
gatewayPortUse different ports for co-existing environments
gatewayHttpsPortSame as above, for HTTPS
secretsEnvironment-specific vault paths

Git Branch Mapping (git) ​

Nanokit can associate Git branches with specific environments.

yaml
git:
  environments:
    main: production
    develop: stage

When you run any command that takes -e/--env without passing it explicitly, Nanokit looks up the current Git branch in this map and uses the mapped environment as the default. It prints a line saying so:

ℹ Branch 'develop' → environment 'stage' (git.environments)

Precedence:

  1. An explicit -e/--env flag — always wins.
  2. git.environments[<current branch>].
  3. The command's own default (local for most commands, stage for deploy).

Exact branch names only — no wildcards

The lookup is a plain key match. "feature/*": preview does not work: it would only ever match a branch literally named feature/*. List branch names explicitly, or pass -e in your preview-environment tooling.

An absent map, an unmapped branch, a detached HEAD, or a non-git directory are all silent no-ops — resolution is best-effort and never blocks a command.


Environment-Level Properties ​

PropertyTypeDescription
infraobjectOverride the infrastructure provider and settings
servicesobjectOverride service-level properties (image, port, host, env, start, etc.) — and restrict membership
databasesobjectOverride database-level properties
deployobjectOverride deployment target and method
gatewayPortnumberOverride the HTTP gateway port
gatewayHttpsPortnumberOverride the HTTPS gateway port
secretsobjectEnvironment-specific secrets

Common Patterns ​

Local Development Override ​

yaml
services:
  web:
    runtime: node
    port: 3000

environments:
  local:
    infra:
      provider: docker           # Use Docker instead of AWS
    services:
      web:
        start: npm run dev       # Dev server instead of production build
        host: web.my-app.localhost
        env:
          NODE_ENV: development

Multi-Environment Staging ​

yaml
environments:
  stage:
    gatewayPort: 8080            # Different port to avoid conflicts
    gatewayHttpsPort: 8443
    infra:
      provider: docker
    services:
      web:
        host: web.my-app.stage
        port: 8100
    deploy:
      target: root@10.0.0.5
      sshKey: ~/.ssh/id_staging
      method: rsync
      prepare: npm install --prefer-offline

Disabling Services Per Environment ​

Two ways, with different semantics:

yaml
environments:
  stage:
    services:
      hub:
        instances: 0    # listed, but scaled to zero — kept in the config surface

Or simply omit it from a non-empty environments.stage.services block, which excludes it entirely by the membership rule above.

Production with Public Domains ​

yaml
environments:
  production:
    infra:
      provider: digitalocean
    services:
      web:
        runtime: node
        host: https://my-app.com
    deploy:
      target: root@185.47.172.104
      sshKey: vault://infra/ssh#private_key
      method: rsync

Viewing the Resolved Config ​

There is no nkapp config <env> read subcommand — nkapp config only writes. To inspect what an environment actually resolves to:

bash
nkapp plan -e <env>       # the fully merged reconcile plan (read-only)
nkapp env list -e <env>   # the merged environment-variable view
nkapp doctor              # schema validation

Tip.nkapp plan is invaluable for debugging merge and membership issues — it shows exactly which services Nanokit considers in scope for a given environment.