expose-service
- Repo stars 2
- Author updated Live
- Author repo anton
- Domain
- AI
- Compatible agents
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- Trust score
- 88 / 100 · community maintained
- Author / version / license
- @wcygan · no license declared
- Token usage
- Lean
- Setup complexity
- Plug-and-play
- External API key
- Not required
- Operating systems
- macOS
- Runtime requirements
- No special requirements
- Permissions
-
- Read-only
- Write / modify
- Network behavior
- Local-only
- Install commands
- 26 variants
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: expose-service
description: Expose a workload for access. Four paths: envoy-internal (LAN via split-horizon DNS), Tailscale…
category: ai
runtime: no special runtime
---
# expose-service output preview
## PART A: Task fit
- Use case: Expose a workload for access. Four paths: envoy-internal (LAN via split-horizon DNS), Tailscale Ingress (internal remote HTTP with browser-trusted TLS), Tailscale Service annotation (raw TCP / non-HTTP), envoy-external + Cloudflare tunnel (genuinely public, requires explicit approval). Handles HTTPRoute authoring, DNSEndpoint for secondary domains, and per-domain cert wiring..
- Inputs: target material, constraints, expected output, and acceptance criteria.
- Evidence boundary: follow “Exposure policy (ADR 0012, amended 2026-04-17) / Gateway-choice matrix / Workflow” and do not present inference as author intent.
## PART B: Execution result
- **01** The card summarizes the use case; runtime output centers on “Expose a workload for access. Four paths: envoy-internal (LAN via split-horizon DNS), Tailscale Ingress (internal remote HTTP with browser-trusted TLS), Tailscale Service annotation (raw TCP / non-HTTP), envoy-external + Cloudflare tunnel (genuinely public, requires explicit approval). Handles HTTPRoute authoring, DNSEndpoint for secondary domains, and per-domain cert wiring.”.
- **02** When the source has headings, the agent prioritizes “Exposure policy (ADR 0012, amended 2026-04-17) / Gateway-choice matrix / Workflow” so the result follows the author’s structure.
- **03** Typical output includes task judgment, concrete steps, required commands or file edits, validation, and follow-up options.
- **04** Risk context follows the fingerprint: read files, write/modify files; mostly runs locally; usually needs no extra API key.
## Running Rules
- read files, write/modify files; mostly runs locally; usually needs no extra API key.
- Validate with a small sample before expanding scope.
- Return the result, validation criteria, and next iteration options. The source does not require a stable slash command. After installation, invoke the skill by name and describe the task.
Name target files or source material, expected output, forbidden changes, and whether network or shell access is allowed. Permission fingerprint: read files, write/modify files.
Start with a small task and check whether the result follows “Exposure policy (ADR 0012, amended 2026-04-17) / Gateway-choice matrix / Workflow”. Inspect diffs, logs, previews, or tests before expanding scope.
Confirm the final output includes a concrete result, evidence, and next action. If it stays generic, tighten inputs, boundaries, and acceptance criteria.
---
name: expose-service
description: Expose a workload for access. Four paths: envoy-internal (LAN via split-horizon DNS), Tailscale…
category: ai
source: wcygan/anton
---
# expose-service
## When to use
- Expose a workload for access. Four paths: envoy-internal (LAN via split-horizon DNS), Tailscale Ingress (internal remo…
- Use it when the task has clear inputs, repeatable steps, and validation criteria.
## What to provide
- Target material, scope, expected result, and forbidden changes.
- Whether network, commands, file writes, or external services are allowed.
## Execution rules
- Organize steps around “Exposure policy (ADR 0012, amended 2026-04-17) / Gateway-choice matrix / Workflow” and keep inference separate from source facts.
- read files, write/modify files; mostly runs locally; usually needs no extra API key.
- Validate with a small sample before expanding the task.
## Output requirements
- Return the deliverable, key evidence, validation method, and next action.
- Mark missing information as unknown; do not invent commands, platforms, or dependencies. The author source anchors workflow facts; repository files anchor sources and commands; Fluxly only adds fit, limitations, and quality judgment.
skill "expose-service" {
input -> user goal + target files + boundaries + acceptance criteria
context -> Exposure policy (ADR 0012, amended 2026-04-17) / Gateway-choice matrix / Workflow
rules -> SKILL.md triggers / order / output contract
runtime -> no special runtime | read files, write/modify files | mostly runs locally
guardrails -> usually needs no extra API key + small-sample validation + diff/log review
output -> copyable result + checklist + next iteration
} Expose a service
Task skill for routing traffic to a workload. Anton has three supported access paths; architecture deep-dive lives in kubernetes/apps/network/AGENTS.md.
Exposure policy (ADR 0012, amended 2026-04-17)
Pick by who needs to reach the workload, not by what feels easiest:
- LAN clients only →
envoy-internalHTTPRoute. Split-horizon DNS viak8s-gatewayreturns the internal LB to LAN clients. - Off-LAN HTTP admin UI (browser) → Tailscale
Ingress(ingressClassName: tailscale+tls.hosts: [<slug>]). The operator provisions a Let's Encrypt cert on<slug>.<tailnet>.ts.net; browsers trust it natively. Recipe A inreferences/tailscale.md. - Off-LAN raw TCP or non-HTTP → Tailscale Service annotation (
tailscale.com/expose: "true"). No TLS termination on the proxy — clients reach whatever the backend serves on its target port. Recipe B inreferences/tailscale.md. - LAN clients AND off-LAN HTTP access →
envoy-internalHTTPRoute plus a TailscaleIngressfor the same workload. LAN users keep the<app>.${SECRET_DOMAIN}URL; remote ops use<slug>.<tailnet>.ts.net. - Genuinely public (audience that cannot be required to join the tailnet) →
envoy-externalHTTPRoute + Cloudflare tunnel. Requires explicit user approval before authoring.
Do not pick envoy-external to solve "I want to reach this from my laptop off-LAN" — that is what Tailscale is for now (ADR 0012). Shipping something public and pulling it back is irreversible (cached DNS, search engines, external referrers); Tailscale is reversible.
⚠️ Throughput caveat (ADR 0012 amendment). Tailscale proxies default to userspace netstack and on v1.84+ hit upstream bug tailscale/tailscale#16198; SPA payloads over the tailnet run ~12 KB/s from a macOS peer. LAN clients are unaffected. Use Tailscale for off-LAN admin access, not for heavy data paths, until the operator ships 1.98.x. Full context in
references/tailscale.md.
Gateway-choice matrix
| Use case | Path | Hostname | TLS in browser? | Reaches |
|---|---|---|---|---|
| LAN only (default) | envoy-internal HTTPRoute |
<app>.${SECRET_DOMAIN} |
Yes (cert-manager) | LAN → k8s-gateway DNS → cluster |
| Off-LAN HTTP admin UI | Tailscale Ingress (ingressClassName: tailscale) |
<slug>.<tailnet>.ts.net |
Yes (Let's Encrypt via MagicDNS) | Tailnet device → operator proxy → Service |
| Off-LAN raw TCP / non-HTTP | Tailscale Service annotation | <slug>.<tailnet>.ts.net |
No (TCP pass-through) | Tailnet device → operator proxy → Service |
| LAN + off-LAN HTTP | HTTPRoute and Tailscale Ingress |
both above | Yes on both | either path resolves independently |
| Public (requires approval) | envoy-external HTTPRoute |
<app>.${SECRET_DOMAIN} |
Yes (Cloudflare) | Internet → Cloudflare tunnel → cluster |
App on a second domain (${SECRET_DOMAIN_TWO}) |
envoy-external HTTPRoute |
<app>.${SECRET_DOMAIN_TWO} |
Yes | also needs an explicit DNSEndpoint — see references/secondary-domain.md |
Both envoy gateways live in namespace network. HTTPRoutes must set parentRefs[].namespace: network (cross-namespace) and sectionName: https (attach to the TLS listener, not port 80). Tailscale exposure does not use a Gateway-API HTTPRoute — it uses either a networking.k8s.io/v1 Ingress (Recipe A) or a Service annotation (Recipe B).
No bundling. Tailscale exposure changes ship on their own PR, never mixed with Cilium / gateway / CNI changes (ADR 0012).
Workflow
- Confirm the path against the exposure policy above. If
envoy-externalis on the table, verify the user has approved public exposure before writing anything. "I want to reach it off-LAN" is a Tailscale reason, not anenvoy-externalreason. - Author the resource(s):
- HTTPRoute path (
envoy-internalorenvoy-external) — writekubernetes/apps/<ns>/<app>/app/httproute.yamlusing the template inreferences/authoring-httproute.md, or use the chart-values variant (Workflow B) if the chart supports it (bjw-sapp-template). - Tailscale Ingress (Recipe A, HTTP admin UIs) — write an
IngresswithingressClassName: tailscaleandtls.hosts: [<slug>]. Prefer the chart's owningress.*values knob when available. Recipe:references/tailscale.md. - Tailscale Service annotation (Recipe B, raw TCP / non-HTTP) — add
tailscale.com/expose: "true"andtailscale.com/hostname: <short-slug>to the Service's annotations. Prefer the chart'sservice.annotationsknob over a Kustomize patch. Recipe:references/tailscale.md. - Combined (HTTPRoute + Tailscale Ingress) — do both; they are independent and don't conflict. Same-PR bundling of Tailscale work with Cilium / gateway / CNI changes is forbidden (ADR 0012).
- HTTPRoute path (
- Wire any new manifest file into
kubernetes/apps/<ns>/<app>/app/kustomization.yaml. Annotation-only changes via chart values need no kustomization edit. - If the hostname is on a secondary domain (not
${SECRET_DOMAIN}), also add aDNSEndpointresource and verify the gateway's cert listener covers the domain — seereferences/secondary-domain.md. Skipping this is the single most common footgun in this skill. (Does not apply to Tailscale — MagicDNS handles its own.) - Ship it. If any
*.sops.*files are new, runsops -e -i <file>to encrypt. Then commit + push, andtask reconcile(or wait for the Flux interval). - Verify. Run the checks in
references/verify.md; for the Tailscale path, also verify as documented inreferences/tailscale.md.
Pre-commit checklist
Common:
- Path choice matches policy (LAN-only → internal; off-LAN HTTP → Tailscale Ingress; off-LAN raw TCP → Tailscale annotation; external only when explicitly approved)
- Diff contains Tailscale changes only — no Cilium / gateway / CNI edits in the same commit (ADR 0012)
- No plaintext secrets: any
*.sops.*files showencryptedviasops filestatus
HTTPRoute path:
-
parentRefs[0].nameisenvoy-externalorenvoy-internal(never a Service name) -
parentRefs[0].namespace: networkset (cross-namespace attach is required) -
parentRefs[0].sectionName: httpsset (attach to TLS listener, not port 80) -
backendRefs[].namematches an actualServicein the app's namespace - HTTPRoute (and
DNSEndpoint, if any) listed inapp/kustomization.yaml - If hostname is on
${SECRET_DOMAIN_TWO}→DNSEndpointresource present - If a new second-domain cert was added → gateway listener's
tls.certificateRefsupdated
Tailscale Ingress path (Recipe A):
-
ingressClassName: tailscaleset; nospec.rules[].host(operator readstls.hosts) -
tls.hosts[0]is a short slug (no dots, no tailnet name embedded) -
defaultBackend.servicepoints at an actualServicein the app's namespace - Ingress listed in
app/kustomization.yaml(or delivered via chartingress.*values) - No real tailnet name committed anywhere (use
<tailnet-name>.ts.netplaceholder in docs)
Tailscale Service annotation path (Recipe B, non-HTTP / raw TCP only):
- Annotation is on the Service the chart produces, not on a wrapping resource
-
tailscale.com/hostnameis a short slug (no dots, no tailnet name embedded) - Workload is genuinely non-HTTP — if it's a browser UI, use Recipe A instead
- No real tailnet name committed anywhere
Canonical in-tree examples
Read the live manifest rather than a frozen copy:
- Public, app-template inline
route:values (Workflow B) →kubernetes/apps/default/echo/app/helmrelease.yaml - Internal, standalone HTTPRoute (Workflow A) →
kubernetes/apps/observability/kube-prometheus-stack/app/httproute.yaml - DNSEndpoint shape (CNAME to tunnel on a secondary domain) →
kubernetes/apps/network/cloudflare-tunnel/app/dnsendpoint.yaml
Further reading
| Reference | When to read |
|---|---|
references/authoring-httproute.md |
Writing the HTTPRoute YAML (Workflow A standalone, Workflow B route-in-values) |
references/secondary-domain.md |
Hostname on ${SECRET_DOMAIN_TWO} or any non-primary domain |
references/verify.md |
After deploy, or when an HTTPRoute exists but the app is unreachable |
references/envoy-gateway.md |
Background on the in-cluster gateway controller (Gateway specs, LB IPs, policies) |
references/cloudflare-tunnel.md |
Background on the public ingress path (cloudflared, http2 transport, origin config) |
references/tailscale.md |
What Tailscale does — and does not — do in this cluster |
Related skills
- Architecture / traffic flow / debug commands →
kubernetes/apps/network/AGENTS.md - Pattern reference for HelmRelease / ks.yaml →
anton-repo-conventions - Triaging an HTTPRoute that exists but the app is unreachable →
debug-flux-reconciliation
Decide Fit First
Design Intent
How To Use It
Boundaries And Review