Exposure Model#
Luma separates the control plane from the data plane.
Roles#
- Cloudflare: DNS automation, optional proxy, optional Tunnel public hostname.
- Traefik: main public HTTP/HTTPS ingress for the
cnedge, plustcp-relaypublished ports. - Tailscale: management network and explicit relay path for
homeor enabled custom-region services. - Luma Control: deployment control plane, driving the Nomad HTTP API.
- Nomad / Docker: runtime execution layer.
Tailscale is not the default business data plane. It only carries data traffic when a service explicitly chooses exposure: tailscale-relay.
Exposure modes#
cn-edge#
Primary domestic public services.
User -> Cloudflare DNS -> CN Traefik -> CN service
Use for:
- main websites;
- public Web/API;
- login, payments, dashboards, normal product traffic.
Manifest:
name: app
image: ghcr.io/your-org/app:latest
region: cn
exposure: cn-edge
domain: app.example.com
port: 3000
replicas: 2
Luma generates:
stacks/cn/app/app.nomad.json;- Traefik service tags for the Nomad provider;
- Cloudflare DNS record pointing to the configured CN edge target.
By default, public traffic enters through one Traefik instance constrained to ${meta.region}=cn and ${meta.ingress}=true. If you have multiple CN nodes, DNS still points to the configured edge target; Traefik receives the request there and forwards to matching service allocations discovered through the Nomad provider.
tailscale-relay#
Home or custom-region services exposed through the existing edge over Tailscale. For a custom region, run luma region update render --exposure tailscale-relay before selecting that exposure in the app manifest. See the upgrade and rollout guide.
User -> Cloudflare DNS -> CN Traefik -> Tailscale -> home service
Use for:
- low-traffic tools;
- preview environments;
- home admin panels;
- services that are acceptable to depend on the home network.
Do not use for:
- core APIs;
- login or payment;
- large downloads;
- high-frequency product traffic.
Manifest:
name: home-panel
image: ghcr.io/your-org/home-panel:latest
region: home
exposure: tailscale-relay
domain: panel.example.com
port: 8080
publishPort: 8080
replicas: 1
relay:
host: home-1.your-tailnet.ts.net
Luma generates:
stacks/home/home-panel/home-panel.nomad.json, which publishes the service port on the home node;routes/home-panel.yml, which Traefik loads through the file provider;- Cloudflare DNS record pointing to the configured CN edge target.
Operational requirement:
- the home host firewall should only allow the published service port from the Tailscale interface or from the CN edge Tailscale IP;
routes/must be available on the CN Traefik node at/opt/luma/routes.
tcp-relay#
Public native TCP services through a dedicated Traefik TCP entrypoint.
Client -> Cloudflare DNS -> edge Traefik TCP entrypoint -> task host port
Use for:
- MySQL or another non-HTTP protocol that must be reachable from the public internet;
- a service where one public port maps to one backend service.
Do not use for:
- multiplexing multiple ordinary MySQL services on the same port by hostname;
- public exposure without database credentials, IP allowlists, or firewall controls.
Manifest:
name: ledger-db
image: mysql:8.4.9
region: home
node: lab
exposure: tcp-relay
domain: ledger-db.example.net
port: 3306
publishPort: 3306
Luma generates:
stacks/home/ledger-db/ledger-db.nomad.json, which publishes the service port in host mode;- Traefik service update for the derived
tcp-3306entrypoint and host-mode published port; routes/ledger-db.yml, which uses Traefiktcp.routerswithHostSNI("*")/HostSNI(\*`)`;- Cloudflare DNS record pointing to the configured edge target.
Ordinary MySQL clients do not start with an HTTP Host header, and should not be assumed to provide reliable TLS SNI before the server handshake. Treat tcp-relay as port-exclusive: one published port routes to one TCP service.
cloudflare-tunnel#
Home or private services exposed directly through Cloudflare Tunnel.
User -> Cloudflare -> cloudflared -> service
Use for:
- home services without a public IP;
- low-frequency tools where you prefer Cloudflare to terminate the public path;
- services that should not route through the CN Traefik node.
Manifest:
name: home-tool
image: ghcr.io/your-org/home-tool:latest
region: home
exposure: cloudflare-tunnel
domain: tool.example.com
port: 8080
replicas: 1
tunnel:
tokenEnv: CLOUDFLARE_TUNNEL_TOKEN
Luma generates:
- a service stack containing the app;
- a
cloudflaredsidecar service using${CLOUDFLARE_TUNNEL_TOKEN}.
Cloudflare Tunnel public hostname configuration is still managed in Cloudflare. Luma skips normal DNS A-record sync for this mode.
external-edge#
Global services with their own overseas/public edge.
User -> Cloudflare DNS -> external/global edge -> global service
Use for:
- AI gateway;
- overseas API gateway;
- proxy services;
- low-frequency services that must execute outside the CN network.
Manifest:
name: ai-gateway
image: ghcr.io/your-org/ai-gateway:latest
region: global
exposure: external-edge
domain: ai.example.com
port: 3000
replicas: 1
dns:
target: 198.51.100.10
Luma generates:
stacks/global/ai-gateway/ai-gateway.nomad.json;- Traefik service tags for the global edge environment;
- Cloudflare DNS record pointing to
dns.target.
Use dns.target to choose the public/global edge IP for this service. Without a separate global edge target, a global workload can still be scheduled in region: global, but public HTTP traffic will not magically enter through every global node.
none#
Internal services and workers.
No public route
Use for:
- workers;
- queue consumers;
- internal jobs;
- backup tasks.
Manifest:
name: fetch-worker
image: ghcr.io/your-org/fetch-worker:latest
region: global
exposure: none
replicas: 1
env:
QUEUE_URL: redis://redis:6379/0
Luma generates:
- a stack with placement constraints;
- no Traefik labels;
- no DNS record.
Decision rule#
- Default public product traffic:
cn-edge. - Home service through the domestic edge:
tailscale-relay. - Public native TCP service:
tcp-relay. - Home/private service through Cloudflare:
cloudflare-tunnel. - Overseas public execution:
external-edge. - Workers and internal services:
none.