Appearance
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:
- Global Spec: Load all properties from the top-level blocks.
- Environment Sync: Merge the matching key from
environments(e.g.,local). - 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>.servicesis absent or empty, every root service applies to that environment, each merged with its (possibly absent) override. - If
environments.<env>.serviceslists 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_onor adb://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:
| Block | Example Override |
|---|---|
infra | Change provider from aws to docker for local dev |
runtimes | Change global images or env vars per environment |
services | Change port, host, commands, env vars per environment (see membership rule above) |
databases | Change engine, version or branching per environment |
deploy | Set different SSH targets per environment |
gatewayPort | Use different ports for co-existing environments |
gatewayHttpsPort | Same as above, for HTTPS |
secrets | Environment-specific vault paths |
Git Branch Mapping (git)
Nanokit can associate Git branches with specific environments.
yaml
git:
environments:
main: production
develop: stageWhen 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:
- An explicit
-e/--envflag — always wins. git.environments[<current branch>].- The command's own default (
localfor most commands,stagefordeploy).
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
| Property | Type | Description |
|---|---|---|
infra | object | Override the infrastructure provider and settings |
services | object | Override service-level properties (image, port, host, env, start, etc.) — and restrict membership |
databases | object | Override database-level properties |
deploy | object | Override deployment target and method |
gatewayPort | number | Override the HTTP gateway port |
gatewayHttpsPort | number | Override the HTTPS gateway port |
secrets | object | Environment-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: developmentMulti-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-offlineDisabling Services Per Environment
Two ways, with different semantics:
yaml
environments:
stage:
services:
hub:
instances: 0 # listed, but scaled to zero — kept in the config surfaceOr 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: rsyncViewing 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 validationTip.
nkapp planis invaluable for debugging merge and membership issues — it shows exactly which services Nanokit considers in scope for a given environment.