Skip to content

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. Application services run on the shared 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 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 under 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 show a security warning until you add the root certificate to your system's trust store.

Trusting the Local CA — use the command ​

bash
nkapp host trust

This is the supported path, and it is the one to use. It locates the root CA wherever it actually lives (project, environment-scoped, or mock-VPS layout) and installs it into the right stores for your platform:

PlatformWhat it does
macOSsecurity add-trusted-cert into the System Keychain
Linuxcopies into /usr/local/share/ca-certificates + update-ca-certificates, and the NSS databases used by Chrome/Firefox
WSL 2both of the above, and bridges the trust into the Windows certificate store

You only need to do this once per machine (or once per project if you use persistenceMode: project). Fully restart your browser afterwards.

Local/mocked environments only

host trust is for local (and mock-VPS) environments. Remote environments use publicly trusted Let's Encrypt certificates, so there is nothing to trust — the command says so and switches to local rather than doing something surprising.

Where the root certificate actually lives ​

If you need the file itself — for a browser that keeps its own store, or a container that must trust it — it is the Caddy PKI root, not a top-level .pem:

.nanokit/gateway/data/caddy/pki/authorities/local/root.crt

Environment-scoped gateways use .nanokit/gateway-<env>/… instead, and a VPS gateway keeps it under /var/lib/nanokit/gateway/…. nkapp host trust checks all of these, which is why it is more reliable than a hard-coded path.

Important.Firefox maintains its own certificate store independently of the operating system. nkapp host trust writes to the NSS databases on Linux/WSL; if your profile is not picked up, import root.crt manually via Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import.

Tip. If you regenerate the CA (e.g. after deleting .nanokit/gateway/), re-run nkapp host trust. Use persistenceMode: global on a VPS to avoid losing issued certificates across redeploys.


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:

yaml
# 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:

yaml
# 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.

If a service with its own TLS layer (e.g. a mail server) cannot read the mounted certificate after a deploy, repair the permissions with nkapp host certs-fix <env> rather than by hand over SSH.


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: 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
bash
# Set via nkapp secrets (recommended)
nkapp secrets set CLOUDFLARE_API_TOKEN=<your-token>
nkapp secrets set CLOUDFLARE_ZONE_ID=<your-zone-id>

You can also store the token with nkapp provider auth cloudflare, which writes it to .nanokit/auth.json (mode 0600, git-ignored) instead of .env.

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.

Cloudflare is DNS-only

Cloudflare is not a compute target. It manages DNS records (infra.dns: cloudflare); the compute providers are docker, aws, azure, gcp and digitalocean. See Providers.

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. Skip it for a single run with nkapp deploy --no-dns (useful when DNS is being managed by nkapp migrate instead).

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.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 ​

yaml
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:

yaml
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.

yaml
# 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.
adminEmailstring—Contact 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.
dnsstring—DNS provider for auto-sync. Currently supports cloudflare.

Note. Changing gatewayPort or gatewayHttpsPort requires recreating the gateway (nkapp destroy then nkapp up, or nkapp deploy for a remote environment), 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:.