Skip to Content
NetworkingEdge Networking

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:

  1. Resolves the host: values declared on each service.
  2. Generates a Caddy configuration covering every virtual host.
  3. Hot-reloads Caddy via its Admin API — zero downtime, no container restarts.
  4. 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.pem

Trusting 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.pem

Linux

sudo cp .nanokit/gateway/caddy-root-ca.pem \ /usr/local/share/ca-certificates/nanokit-local-ca.crt sudo update-ca-certificates

Windows / WSL 2

  1. Copy caddy-root-ca.pem to a Windows-accessible path.
  2. Open Runcertmgr.msc.
  3. Navigate to Trusted Root Certification AuthoritiesCertificates.
  4. Right-click → All TasksImport, then follow the wizard.

[!IMPORTANT] Firefox maintains its own certificate store independently of the operating system. After trusting the CA system-wide, open Firefox → SettingsPrivacy & SecurityCertificatesView CertificatesAuthoritiesImport, 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 use persistenceMode: global on 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:

  1. Detects that the domain is not a .localhost address.
  2. Initiates an ACME HTTP-01 or DNS-01 challenge with Let’s Encrypt (default) or ZeroSSL.
  3. Stores the issued certificate and private key internally.
  4. 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: global

With 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: cloudflare

Then provide your Cloudflare credentials as environment variables (never hard-code them):

VariableRequiredDescription
CLOUDFLARE_API_TOKEN✅ YesAPI token with Zone:DNS:Edit permission
CLOUDFLARE_ZONE_IDOptionalExplicit 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_TOKEN to source control. Always inject it via nkapp secrets set or a .env file 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.com

With 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.localhost

After nkapp up, your services are available at:

  • https://myapp.nanokit.localhost
  • https://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 on nanokit-net by 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.

HeaderValuePurpose
Strict-Transport-Securitymax-age=31536000; includeSubDomainsEnforces HTTPS for 1 year (HSTS)
X-Content-Type-OptionsnosniffPrevents MIME-type sniffing
X-Frame-OptionsSAMEORIGINBlocks 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 includeSubDomains means 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: cloudflare

gateway Options

OptionTypeDefaultDescription
httpsbooleantrueEnable TLS on all virtual hosts. Disable only for debugging.
adminEmailstringContact email for ACME certificate registration. Required for public domains.

infra Options

OptionTypeDefaultDescription
gatewayPortinteger80Host port the gateway listens on for HTTP traffic.
gatewayHttpsPortinteger443Host port the gateway listens on for HTTPS traffic.
persistenceModestringprojectglobal stores TLS state outside the project lifecycle (recommended for VPS). project scopes state to the project.
dnsstringDNS provider for auto-sync. Currently supports cloudflare.

[!NOTE] Changing gatewayPort or gatewayHttpsPort requires 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:.