Infrastructure Provisioning
Nanokit turns any server — from a bare DigitalOcean droplet to a managed cloud cluster — into a fully-managed container runtime. The infra and deploy blocks in your nanokit.yml are the single source of truth for how your infrastructure is provisioned, connected, and maintained.
Overview
When you run nkapp deploy or nkapp host setup, Nanokit performs a fully automated provisioning sequence:
- Connects to the target host over SSH using your configured key.
- Bootstraps the host if necessary — installing Docker, configuring UFW, and creating the Nanokit directory tree.
- Opens an SSH tunnel to the remote Docker socket so all subsequent Docker commands are proxied securely through your existing connection.
- Synchronises your project code to the remote host using the configured method (
rsyncorgit). - Prunes any stale build artefacts listed in
prune. - Runs your
preparecommand inside an isolated Docker container on the remote host. - Starts or updates your services via the remote Docker daemon.
[!NOTE] Nanokit never requires you to open the Docker port to the internet. All Docker API traffic travels inside the encrypted SSH tunnel, so the daemon socket never leaves localhost on the remote machine.
VPS / Bare-Metal Setup
Follow these steps to go from a brand-new server to a running Nanokit deployment.
Step 1 — Provision a VPS
Create a server with any supported provider (see the Supported Providers table below). The only prerequisites are:
- A Debian/Ubuntu-based OS (recommended: Ubuntu 22.04 LTS or later)
- Root or sudo SSH access
- Ports 22, 80, and 443 reachable from the internet
Step 2 — Add Your SSH Key
Upload your public key to the server so Nanokit can authenticate without a password:
ssh-copy-id -i ~/.ssh/id_rsa.pub root@<YOUR_SERVER_IP>Verify the connection works before proceeding:
ssh -i ~/.ssh/id_rsa root@<YOUR_SERVER_IP> "echo connected"Step 3 — Point the Environment at the Host
Set the SSH deploy target for the environment in nanokit.yml:
nkapp config deploy set target=root@<YOUR_SERVER_IP> -e production
nkapp config deploy set sshKey=~/.ssh/id_rsa -e productionThis writes the deploy.target / deploy.sshKey fields into the
environments.production block, which all subsequent remote commands use.
Step 4 — Bootstrap the Host
Run the automated setup wizard against your chosen environment:
nkapp host setup productionNanokit will SSH into the host and automatically:
- Install Docker Engine (latest stable)
- Configure UFW to allow ports 80 and 443 (HTTP/HTTPS) and keep 22 open
- Create
/opt/nanokit/projects/<name>/and the Caddy gateway directory tree - Pull the Nanokit gateway image
[!TIP] You can re-run
nkapp host setup <env>at any time to repair or upgrade a host. The command is idempotent — it will skip steps that are already complete.
Step 5 — Configure nanokit.yml
Add the infra and deploy blocks to your project configuration:
infra:
provider: docker # use the VPS Docker daemon
deploy:
target: root@203.0.113.42 # SSH connection string
sshKey: ~/.ssh/id_rsa # path to your private key
sshPort: 22 # default; omit if unchanged
method: rsync # rsync | git
runtime: node:22-slim # Docker image for the prepare step
prepare: npm ci && npm run build
prune:
- dist
- apps/*/.nextStep 6 — Deploy
nkapp deploy --env productionNanokit connects, syncs, builds, and starts your services. The first deploy on a fresh host may take a minute or two while Docker images are pulled.
[!IMPORTANT] Ensure the
sshKeypath resolves correctly on the machine running the CLI. Nanokit supports~/, relative paths, andvault://URIs for secret-manager-backed keys.
The SSH Tunnel Architecture
Nanokit never exposes the Docker daemon port (2375/2376) to the public internet. Instead, it creates a local SSH port-forward to the remote Unix socket each time a session begins:
Your Machine Remote VPS
┌──────────────────────┐ ┌──────────────────────────────────┐
│ nkapp CLI │ │ sshd │
│ │ SSH │ │
│ Docker client ─────┼──tunnel───┼──► /var/run/docker.sock │
│ (via tunnel) │ :22 only │ │
└──────────────────────┘ └──────────────────────────────────┘The tunnel is established immediately after authentication and torn down when the CLI session ends. This means:
- No extra ports need to be opened in your firewall.
- mTLS is not required — SSH handles transport security.
- All
dockerAPI calls — image pulls, container starts, volume management — travel through this single authenticated channel.
[!WARNING] If your SSH key is passphrase-protected and no SSH agent is running, the CLI will prompt for the passphrase. In CI/CD pipelines, use a dedicated key stored in your secrets manager (e.g.,
sshKey: vault://secrets/deploy-key) to avoid interactive prompts.
Synchronisation Methods
The deploy.method key controls how your source code is copied to the remote host before the build step runs.
rsync (default)
Nanokit runs rsync over the existing SSH connection, transferring only changed files:
deploy:
method: rsyncCharacteristics:
- Fastest incremental transfers — only deltas are sent
- Respects
.gitignoreand a built-in exclusion list (node_modules,.git, etc.) - Works without Git being installed on the remote
- Ideal for large projects where minimal transfer time matters
git
Nanokit pushes a Git bundle or runs a remote git pull on the host:
deploy:
method: gitCharacteristics:
- Full Git history is available on the remote (useful if
preparerunsgit log, changelogs, etc.) - Requires Git to be installed on the remote host (Nanokit installs it during
host setup) - Slightly slower than rsync for large repositories with many files
[!TIP] Use
rsyncfor frontend/Node.js monorepos with largenode_modules(even though they are excluded, the directory tree walk is faster). Usegitwhen yourpreparestep relies on Git metadata.
Isolated Build (The prepare Step)
After code is synced, Nanokit runs your prepare command inside an ephemeral Docker container on the remote host. This container:
- Uses the image specified by
deploy.runtime(e.g.,node:22-slim) - Mounts the synced project directory as its working directory
- Has no access to the host network by default
- Is destroyed immediately after the command exits
deploy:
runtime: node:22-slim
prepare: npm ci && npm run buildThe isolation guarantees that build tooling versions are pinned to the image tag, and that no build artefact can accidentally write outside the project directory.
[!NOTE] The
preparestep runs after pruning and before services are restarted. Ifprepareexits with a non-zero code, the deploy is aborted and the previous running version is left untouched.
Custom Runtimes
You can use any Docker image as the build runtime:
deploy:
runtime: python:3.12-slim # Python project
prepare: pip install -r requirements.txt && python manage.py collectstatic --noinputdeploy:
runtime: golang:1.22 # Go binary build
prepare: go build -o ./bin/server ./cmd/serverCache Pruning
The deploy.prune list specifies directories that should be deleted on the remote before the prepare step runs. This prevents stale build output from interfering with a fresh build:
deploy:
prune:
- dist
- apps/*/.next
- .turboGlob patterns are supported. Paths are relative to the project root on the remote host (/opt/nanokit/projects/<name>/).
[!CAUTION] Be conservative with prune paths. Pruning directories that contain user-uploaded files or persistent runtime data will delete them permanently. Keep build artefacts strictly separated from runtime state.
Supported Providers
The infra.provider key selects where Nanokit manages infrastructure. For VPS and bare-metal deployments, docker is almost always the correct choice.
| Provider | Value | Notes |
|---|---|---|
| Local / VPS Docker | docker | Default. Works on any Docker host, local or remote. |
| Amazon Web Services | aws | EC2 + ECS/Fargate, ECR image build/push, NLB ingress, VPC/subnets, EBS volumes, Route 53 DNS, ECS exec, log retrieval, telemetry. Authenticate via nkapp provider auth aws or standard AWS env vars. |
| DigitalOcean | digitalocean | At parity with AWS: Droplets and App Platform (managed ingress), DOCR image build/push, VPC networking, block volumes, DNS, discover/clean/exec, App Platform log retrieval, and Droplet telemetry via the DO Monitoring API. Authenticate via nkapp provider auth do or DIGITALOCEAN_TOKEN. |
| Google Cloud Platform | gcp | Cloud Run and Compute Engine, Artifact Registry, Cloud DNS — ~25 provider methods. Authenticate via nkapp provider auth gcp or GOOGLE_APPLICATION_CREDENTIALS. |
| Microsoft Azure | azure | Virtual Machines, container services, registry, networking and DNS. Authenticate via nkapp provider auth azure or AZURE_* env vars. |
These are the only supported compute providers, and they are all implemented to
roadmap.v1 parity. Cloudflare is additionally supported as a DNS-only
provider (infra.dns: cloudflare); it is not a compute target.
[!NOTE] For managed-ingress providers (DigitalOcean App Platform, AWS NLB), Nanokit provisions the platform’s own load balancer rather than a self-managed gateway — so the Caddy
ensureGatewaystep is intentionally a no-op on that path.
[!NOTE] The DigitalOcean integration covers the full flow end-to-end; the few remaining gaps (and optional cron/bridge parity) need a live DO account to validate.
Scaling Configuration
Nanokit supports both automatic and manual scaling policies, configured under the infra block.
Manual Scaling
Pin a fixed number of instances with specific roles:
infra:
provider: aws
scaling: manual
instances:
- type: t3.medium
count: 2
role: web
- type: t3.large
count: 1
role: workerAuto-scaling
Let Nanokit (or the underlying cloud provider) adjust capacity within bounds you define:
infra:
provider: digitalocean
scaling: auto
min: 1
max: 10Shared / Adopted Clusters
To deploy into an existing cluster that Nanokit should not modify (e.g., a shared staging cluster managed by another team), set infra.shared: true:
infra:
provider: aws
shared: true # adopt the cluster; do not provision or deprovision nodesVPC and Networking
infra:
provider: aws
vpc: vpc-0abc12345def67890
cidr: 10.0.0.0/16[!NOTE]
vpcandcidrare only relevant for cloud providers that manage VPC networking (AWS, GCP, Azure, DigitalOcean). They are ignored for thedockerprovider.
Remote Directory Structure
When Nanokit provisions a VPS, it creates the following directory tree under /opt/nanokit/:
/opt/nanokit/
└── projects/
└── <project-name>/ ← synced project files live here
├── nanokit.yml
├── apps/
├── packages/
└── .nanokit/
└── gateway/
└── data/
└── caddy/ ← Caddy reverse-proxy configuration & certificates| Path | Purpose |
|---|---|
/opt/nanokit/projects/<name>/ | Project root; destination for rsync/git sync |
.nanokit/gateway/ | Internal Nanokit gateway service files |
.nanokit/gateway/data/caddy/ | Caddy configuration, TLS certificate storage |
[!NOTE] Do not manually edit files under
.nanokit/gateway/. These are managed by Nanokit and will be overwritten on the next deploy.
Troubleshooting
Stale Infrastructure Locks
If a deploy is interrupted (e.g., by a network drop), Nanokit may leave a lock in place to prevent concurrent modifications. Unlock manually with:
nkapp infra unlock --env production[!CAUTION] Only run
infra unlockif you are certain no deploy is actively in progress. Unlocking during an active deploy can leave infrastructure in an inconsistent state.
SSH Connection Refused
- Confirm port 22 is open:
nc -zv <IP> 22 - Check that your public key is in
/root/.ssh/authorized_keyson the remote - Verify
deploy.sshKeypoints to the private key that corresponds to the uploaded public key - If using a non-standard port, set
deploy.sshPortexplicitly
prepare Step Fails on Remote
- SSH into the host and check container logs:
docker logs nanokit-prepare-<id> - Verify the
deploy.runtimeimage is pullable from the remote host (check internet access and Docker Hub rate limits) - Test your
preparecommand locally first using the same Docker image:docker run --rm -v $(pwd):/app -w /app node:22-slim sh -c "npm ci && npm run build"
Docker Not Found After host setup
Occasionally, Docker installation may fail silently if the host’s package index is stale. SSH into the host and run:
apt-get update && apt-get install -y docker.io
systemctl enable --now dockerThen re-run nkapp host setup <env> to let Nanokit verify and complete the setup.
UFW Blocking Traffic
If your application is unreachable on port 80/443, check UFW status on the remote:
ssh root@<IP> "ufw status verbose"Expected output should include 80/tcp ALLOW IN and 443/tcp ALLOW IN. If not, re-run nkapp host setup <env> or add rules manually:
ufw allow 80/tcp
ufw allow 443/tcpRsync Fails with Permission Denied
Ensure the remote /opt/nanokit/projects/<name>/ directory is owned by the user specified in deploy.target. By default this is root:
ssh root@<IP> "ls -la /opt/nanokit/projects/"If a previous setup created the directory with the wrong owner, fix it with:
ssh root@<IP> "chown -R root:root /opt/nanokit/projects/<name>"