Appearance
Networking Overview
Nanokit manages all inbound traffic through a built-in Caddy gateway that runs alongside your containers in every environment — local and remote. You never configure a reverse proxy by hand: declare a host: on a service and Nanokit handles the rest, from DNS record creation to TLS certificate provisioning.
How the Gateway Works
Every Nanokit environment starts a dedicated Caddy instance that acts as the sole public-facing entry point. All application services run on an isolated private Docker network (nanokit-net) and are never exposed directly to the host. The gateway is the only container that binds to host ports.
Internet / Browser
│
▼
┌─────────────┐ :80 / :443
│ Caddy │◄────────────── host ports
│ (gateway) │
└──────┬──────┘
│ nanokit-net (private)
┌─────┴──────┬────────────┐
▼ ▼ ▼
[api svc] [web svc] [worker svc]When you run nkapp up or nkapp deploy, Nanokit:
- Resolves the
host:values declared on each service. - Generates a Caddy configuration covering every virtual host.
- Hot-reloads Caddy via its Admin API — zero downtime, no container restarts.
- Optionally creates or updates DNS records (when Cloudflare is configured).
Note. Caddy's Admin API runs on a loopback address inside the gateway container and is never exposed externally.
Local HTTPS
For local development environments, Nanokit services are reachable on .localhost subdomains (e.g. myapp.nanokit.localhost). Caddy acts as a local certificate authority (CA) and issues self-signed TLS certificates for these domains automatically — no manual certificate generation required.
How It Works
When the gateway first starts, Caddy generates a root CA key pair and stores it in the project's .nanokit/gateway/ directory. It then issues leaf certificates for every *.localhost virtual host on demand. Because the certificates are signed by a CA that exists only on your machine, browsers will show a security warning until you add the root certificate to your system's trust store.
The root CA PEM file lives at:
.nanokit/gateway/caddy-root-ca.pemTrusting the Local CA
You only need to do this once per machine (or once per project if you use persistenceMode: project).
macOS
bash
sudo security add-trusted-cert \
-d -r trustRoot \
-k /Library/Keychains/System.keychain \
.nanokit/gateway/caddy-root-ca.pemLinux
bash
sudo cp .nanokit/gateway/caddy-root-ca.pem \
/usr/local/share/ca-certificates/nanokit-local-ca.crt
sudo update-ca-certificatesWindows / WSL 2
- Copy
caddy-root-ca.pemto a Windows-accessible path. - Open Run →
certmgr.msc. - Navigate to Trusted Root Certification Authorities → Certificates.
- Right-click → All Tasks → Import, then follow the wizard.
Important.Firefox maintains its own certificate store independently of the operating system. After trusting the CA system-wide, open Firefox → Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import, and select
caddy-root-ca.pem.
Tip. If you regenerate the CA (e.g. after deleting
.nanokit/gateway/), you must re-run the trust steps above. Keep the directory under source control or usepersistenceMode: globalon VPS to avoid losing issued certificates.
Public Domain Routing
For remote environments (VPS, cloud), Caddy handles TLS certificate issuance through the ACME protocol — the same standard used by Let's Encrypt and ZeroSSL. No manual certificate management is needed.
How ACME Works in Nanokit
When a service declares a public host: value and the gateway starts on a publicly reachable server, Caddy:
- Detects that the domain is not a
.localhostaddress. - Initiates an ACME HTTP-01 or DNS-01 challenge with Let's Encrypt (default) or ZeroSSL.
- Stores the issued certificate and private key internally.
- Renews certificates automatically before expiry.
To receive certificates, set a contact email so certificate authorities can reach you:
yaml
# nanokit.yml
gateway:
adminEmail: you@example.com
https: trueWarning. The server must be reachable on port 80 and/or 443 from the internet for HTTP-01 challenges to succeed. If you are behind a firewall, use DNS-01 challenges via Cloudflare (see below).
Certificate Persistence
On VPS deployments, use persistenceMode: global to ensure certificates survive across redeployments:
yaml
# nanokit.yml
infra:
persistenceMode: globalWith persistenceMode: project, certificate data is scoped to the project and may be lost on a full teardown. The global mode stores certificate state in a shared volume outside the project lifecycle.
Cloudflare DNS Integration
Nanokit can automatically create and update DNS records in Cloudflare when you deploy. This eliminates manual DNS management: run nkapp deploy and your domain is live.
Setup
Add a dns: block to your infra section:
yaml
# nanokit.yml
infra:
dns: cloudflareThen provide your Cloudflare credentials as environment variables (never hard-code them):
| Variable | Required | Description |
|---|---|---|
CLOUDFLARE_API_TOKEN | ✅ Yes | API token with Zone:DNS:Edit permission |
CLOUDFLARE_ZONE_ID | Optional | Explicit zone ID; auto-detected from domain if omitted |
bash
# Set via nkapp secrets (recommended)
nkapp secrets set CLOUDFLARE_API_TOKEN=<your-token>
nkapp secrets set CLOUDFLARE_ZONE_ID=<your-zone-id>Caution. Never commit
CLOUDFLARE_API_TOKENto source control. Always inject it viankapp secrets setor a.envfile that is listed in.gitignore.
How Auto-Sync Works
Every time you run nkapp up or nkapp deploy, Nanokit reads the host: values on all services and reconciles DNS records in Cloudflare:
- A record → created/updated to point to the server's public IP.
- CNAME record → created/updated when a
cname:target is specified. - Records that are no longer referenced are left untouched (no automatic deletion).
The sync is idempotent: running it multiple times against the same configuration produces the same result.
Note. DNS propagation can take up to a few minutes after a record is created. Nanokit does not wait for propagation — it proceeds immediately after the API call succeeds.
Service Routing
The gateway routes incoming requests to the correct container based on the host: field on each service. No additional configuration is required.
Basic Example
yaml
# nanokit.yml
services:
web:
image: my-org/frontend:latest
host: app.example.com
api:
image: my-org/backend:latest
host: api.example.comWith this configuration, https://app.example.com routes to the web container and https://api.example.com routes to the api container — both through the same Caddy gateway.
Local Development Example
yaml
services:
web:
build: .
host: myapp.nanokit.localhost
api:
build: ./api
host: api.myapp.nanokit.localhostAfter nkapp up, your services are available at:
https://myapp.nanokit.localhosthttps://api.myapp.nanokit.localhost
Port Targeting
If a container exposes multiple ports, specify which one the gateway should forward to:
yaml
services:
app:
image: my-org/app:latest
host: app.example.com
port: 3000 # gateway forwards to container port 3000Note. Services without a
host:declaration are still reachable from other containers onnanokit-netby their service name, but they are not exposed through the gateway.
Security Headers
Nanokit automatically injects a set of security response headers on all gateway-routed responses. You do not need to configure these in your application code.
| Header | Value | Purpose |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Enforces HTTPS for 1 year (HSTS) |
X-Content-Type-Options | nosniff | Prevents MIME-type sniffing |
X-Frame-Options | SAMEORIGIN | Blocks clickjacking via iframes |
These headers are applied at the gateway layer universally — they cannot be overridden by individual services. If your application sets the same headers, the gateway header takes precedence for the above list.
Tip. HSTS with
includeSubDomainsmeans all subdomains of your root domain are also forced to HTTPS. Ensure any non-HTTPS subdomains are migrated before enabling this on a production domain.
Gateway Configuration Reference
The gateway block in nanokit.yml controls the Caddy gateway's behaviour. The infra block controls network-level settings.
yaml
# nanokit.yml (annotated example)
gateway:
https: true
adminEmail: ops@example.com
infra:
gatewayPort: 80
gatewayHttpsPort: 443
persistenceMode: global # or: project
dns: cloudflaregateway Options
| Option | Type | Default | Description |
|---|---|---|---|
https | boolean | true | Enable TLS on all virtual hosts. Disable only for debugging. |
adminEmail | string | — | Contact email for ACME certificate registration. Required for public domains. |
infra Options
| Option | Type | Default | Description |
|---|---|---|---|
gatewayPort | integer | 80 | Host port the gateway listens on for HTTP traffic. |
gatewayHttpsPort | integer | 443 | Host port the gateway listens on for HTTPS traffic. |
persistenceMode | string | project | global stores TLS state outside the project lifecycle (recommended for VPS). project scopes state to the project. |
dns | string | — | DNS provider for auto-sync. Currently supports cloudflare. |
Note. Changing
gatewayPortorgatewayHttpsPortrequires a full environment restart (nkapp down && nkapp up), as Docker port bindings cannot be updated in place.
Network Architecture Summary
┌─────────────────────────────────────────────────┐
│ nanokit.yml │
│ │
│ gateway: │
│ https: true │
│ adminEmail: ops@example.com │
│ │
│ infra: │
│ gatewayPort: 80 │
│ gatewayHttpsPort: 443 │
│ persistenceMode: global │
│ dns: cloudflare │
│ │
│ services: │
│ web: { host: app.example.com } │
│ api: { host: api.example.com } │
└─────────────────────────────────────────────────┘
│
▼
┌───────────────┐ Cloudflare API
│ nkapp deploy │ ───────────────────► DNS A record
└───────┬───────┘ (auto-created)
│
▼
┌───────────────┐ :80 / :443
│ Caddy │◄──────────────── public internet
│ (gateway) │
│ │ ACME → Let's Encrypt / ZeroSSL
│ │ HSTS + security headers
└───────┬───────┘
│ nanokit-net (internal)
┌──────┴──────┐
│ web │ api │ (never exposed directly)
└─────────────┘All complexity — TLS provisioning, DNS sync, header injection, and zero-downtime reloads — is handled by the platform. Your services only need to declare a host:.