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
sudo security add-trusted-cert \
-d -r trustRoot \
-k /Library/Keychains/System.keychain \
.nanokit/gateway/caddy-root-ca.pemLinux
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:
# nanokit.yml
gateway:
adminEmail: you@example.com
https: true[!WARNING] 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:
# 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:
# 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 |
# 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
# 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
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:
services:
app:
image: my-org/app:latest
host: app.example.com
port: 3000 # gateway forwards to container port 3000[!NOTE] 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.
# 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:.