Services & Provisioning
Services are the building blocks of your application. Each service represents a containerized workload that Nanokit orchestrates.
Property Reference
Core Configuration
| Property | Type | Description |
|---|---|---|
image | string | Docker image (e.g., node:20-slim). Optional if a runtime is used. |
build | string | Path to Dockerfile or build context. |
runtime | string | Runtime ID (from the global runtimes block). Also used for auto-discovery. |
port | number | Optional. Public port bound on the host (127.0.0.1). Omit it for internal-only services — they are still routed through the gateway by domain. |
expose | number | Optional. Internal container port. When omitted, Nanokit auto-detects it from the image’s EXPOSE metadata (falling back to 80). See Automatic Port Detection. |
path | string | Local directory to mount as a volume (enables hot-reload during development). |
start | string | Command to run as the container entrypoint. |
host | string | Custom domain(s) for this service. Multiple domains can be comma-separated. |
firewall | string[] | Array of public ports to expose on the VPS host firewall (e.g. ['25/tcp', '587/tcp']). |
depends_on | string[] | Ordered dependencies. Ensures start/stop hierarchy. |
updateStrategy | recreate | rolling | Update strategy. recreate (default) stops then starts. rolling provides zero-downtime. |
instances | number | Number of container replicas. Set to 0 to disable the service in a given environment. |
Environment Variables
| Property | Type | Description |
|---|---|---|
env | object | Key-value pairs injected as environment variables into the container. |
secrets | object | Sensitive values resolved from Vault or other providers. See Secrets. |
Resources & Scaling
| Property | Type | Description |
|---|---|---|
cpu | number | string | CPU limit (e.g., 0.5, 2.0). |
memory | string | Memory limit (e.g., 512mb, 1gb). |
volumes | string[] | Persistent data mappings using source:target syntax. |
scale | object | Auto-scaling configuration for the service. |
Scale Options
| Property | Type | Description |
|---|---|---|
scale.min | number | Minimum number of replicas. |
scale.max | number | Maximum number of replicas. |
scale.cpu_threshold | string | CPU utilization target for scaling (e.g., "70%"). |
Provisioning
| Property | Type | Description |
|---|---|---|
provisioning.install | string | One-time install command. Inherited from the Global Runtime if omitted. |
Host Mapping
The host property maps custom domains to a service via the integrated Caddy gateway:
services:
web:
runtime: node
host: my-app.localhost # Single domain
api:
runtime: node
host: api.my-app.localhost, api.localhost # Multiple domains (comma-separated)In production environments, Caddy automatically obtains SSL certificates via ACME (Let’s Encrypt).
Automatic Port Detection
Ports are optional. If you declare neither expose nor port, Nanokit reads
the port directly from the image’s own EXPOSE metadata and wires the gateway to
it — zero configuration. A standard image such as node:20 (which exposes 3000)
or postgres:16 (5432) just works:
services:
web:
image: my-org/web:latest # image EXPOSEs 3000 → routed automatically
host: my-app.localhostThe detected port is used for the injected PORT environment variable, the
gateway upstream, and the container’s exposed port. Resolution order:
expose— explicit internal port, if set.port— explicit host-bound port, if set.- Image
EXPOSE— the lowest exposed TCP port the image declares. 80— final fallback when the image exposes nothing.
When multiple TCP ports are exposed, Nanokit picks the lowest (application
ports are conventionally lower than auxiliary debug/metrics ports such as 9229).
Set expose explicitly to override the heuristic.
[!NOTE] Auto-detection only fills the internal port. Binding a port on the host still requires an explicit
port(used for raw TCP services like databases or mail). Most HTTP services need neither — they are reached through the gateway by domain.
Declarative Firewall Configuration
The firewall property allows you to define public ports that should be opened on the VPS host firewall. During deployment, the Nanokit CLI automatically configures UFW on the remote machine:
services:
mail:
image: stalwartlabs/stalwart:latest
port: 8080
expose: 8080
host: mail.my-app.com
firewall:
- 25/tcp # SMTP
- 587/tcp # SMTP Submission
- 465/tcp # SMTPS
- 993/tcp # IMAPSWhen you run nkapp deploy, the CLI scans your services, aggregates all firewall rules, and securely provisions them on the remote VPS (e.g. executing sudo ufw allow 587/tcp and reloading UFW) completely transparently.
Update Strategies
recreate (Default)
The container is stopped, removed, and recreated. Brief downtime occurs during the switch.
rolling
A new container is started alongside the old one. Traffic is switched after the new container is healthy. The old container is then removed. Zero downtime.
services:
api:
image: my-app:latest
updateStrategy: rollingBuild & Compilation Strategies
Nanokit handles the transition from source code to container images using a dual-mode strategy designed for both local development and professional CI/CD pipelines.
1. Artifact-based (Pre-build)
If Nanokit detects pre-compiled artifacts (e.g., dist/, .next/, out/, build/) in the service path or project root, it automatically includes them in the Docker context and copies them into the image.
- Workflow:
nx build→nkapp deploy. - Benefit: Faster image creation as compilation happens once outside of Docker. Ideal for CI/CD environments where builds are a separate stage.
2. Source-based (In-Container Build)
If a build command is specified in nanokit.yml, Nanokit executes it inside the Docker container during the image creation phase.
- Workflow:
nkapp deploy(Nanokit handles compilation internally). - Benefit: Guarantees that the code is compiled in an environment identical to the runtime. This is crucial for services with native dependencies (C++/Rust) that must match the target architecture (e.g.
linux/amd64).
services:
api:
runtime: node
build: npm run build # Executed inside the Docker build process
start: node dist/index.js3. Native Dockerfile (Full Control)
If you need complete control over the container environment, you can provide your own Dockerfile. Nanokit will bypass its automatic generation and use yours instead.
- Workflow:
build: trueorbuild: ./Dockerfile. - Benefit: Support for specialized system dependencies, multi-stage builds, and custom base images.
See the dedicated Native Dockerfile page for more details.
Lifecycle & Hooks
Nanokit allows you to hook into different stages of a service’s lifecycle for tasks like database migrations, installation of dependencies, or cleanup.
For detailed information on prestart, poststart, provisioning.install, and more, see the dedicated Lifecycle & Hooks page.
Disabling a Service
Set instances: 0 to completely disable a service in a specific environment:
environments:
stage:
services:
hub:
instances: 0 # Hub is not deployed to stagingDependencies (depends_on)
The depends_on property defines the orchestration order of your project. Nanokit calculates a Directed Acyclic Graph (DAG) to determine which services can start in parallel and which must wait for others.
Common Patterns
1. Database Readiness
The most frequent use case is ensuring an application waits for its database to be healthy before attempting a connection.
services:
api:
runtime: node
depends_on:
- test_mongo_db2. Provisioning Master
When sharing volumes (like node_modules or a shared cache) between services, you can designate one “Master” service to handle the heavy lifting (installation, compilation) by making other services depend on it.
[!TIP] For a more elegant approach, define this dependency at the Runtime Level instead of repeating it in every service.
3. Service Hierarchy
In microservice architectures, you may want to ensure that a gateway or a central authentication service is up before the rest of the stack.
Complete Example
services:
web:
runtime: node
path: ./apps/web
port: 3000
host: my-app.localhost
updateStrategy: rolling
cpu: 1.0
memory: 1gb
depends_on:
- api
env:
NODE_ENV: production
start: npm run start
hooks:
prestart: "npx prisma migrate deploy"
volumes:
- ./uploads:/workspace/uploads[!TIP] Atomic Install: The
provisioning.installcommand is executed only if the shared volume’s sentinel file is missing, preventing redundant installs across distributed clusters.