Expose private service
Skill CervantesVive/agent-skills/skills/infra/expose-private-service
Expose a private-network-only homelab service over HTTPS through an existing Traefik reverse proxy and Cloudflare DNS, using a trusted wildcard cert. Use when adding a new self-hosted service (Docker Compose or otherwise) that should be reachable only over a private network (Tailscale, WireGuard, plain LAN, a cloud VPC's private subnet, etc.), not the public internet.From its SKILL.md
npx -y skills add CervantesVive/agent-skills --skill expose-private-serviceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- 14 days oldThe repository was created 14 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.8 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
Expose a private-network-only service via Traefik + Cloudflare DNS
This pattern works for a host that runs services bound directly to a private
IP (never 0.0.0.0), fronted by a single host-level Traefik instance that
terminates HTTPS with a wildcard Let's Encrypt certificate for a domain like
*.home.example.com (obtained via Cloudflare DNS-01 challenge — no port
80/443 validation needed, so the cert doesn't require public reachability).
"Private IP" is intentionally generic: any address not routable from the
public internet works — a Tailscale/WireGuard overlay address, a plain LAN
IP, a cloud VPC's private subnet address, etc. The reference implementation
below (scripts/expose.sh) is network-agnostic; it just takes the IP as a
BACKEND_IP env var. Tailscale is used in examples purely because it's a
common way to get a stable private IP for a homelab host — swap in whatever
your network uses.
Each service still needs its own explicit Cloudflare DNS A record (there is
no wildcard DNS record, only a wildcard cert) and its own Traefik dynamic
config file. Both records are public in the sense that anyone can query the
DNS name or read the Traefik config — but the target IP is only reachable
over your private network. So the service is DNS-visible but
network-reachable only to hosts on that network.
Files
scripts/expose.sh— idempotent helper that creates the Cloudflare DNS record and writes the Traefik dynamic config file. Invoked in Step 2 below.
When to use this
A new service is running (in Docker, bound to the host's private IP on some
port, e.g. ${BACKEND_IP}:PORT:CONTAINER_PORT in its compose file) and needs
a friendly HTTPS hostname reachable from any device on that private network,
matching the pattern of every other service on this host.
Steps
-
Bind the service to the private IP, not
0.0.0.0. If it's a Docker Compose service, the port mapping should look like:ports: - "${BACKEND_IP}:<host-port>:<container-port>"Check
ss -tlnpfirst to confirm<host-port>isn't already taken by another local service on this host. -
Run the helper script with a Cloudflare API token scoped to DNS edit on your zone, the backend's private IP, plus the three required domain env vars:
ROOT_DOMAIN=example.com BASE_DOMAIN=home.example.com CERT_MAIN_DOMAIN=home.example.com \ CLOUDFLARE_API_TOKEN=<token> BACKEND_IP=<private-ip> ./scripts/expose.sh <subdomain> <host-port>BACKEND_IPcan come from wherever your private network gets its address — e.g.BACKEND_IP="$(tailscale ip -4)"on a Tailscale-connected host, a static LAN IP, or a cloud instance's VPC private IP.This is idempotent — safe to re-run. It will:
- Create
<subdomain>.<BASE_DOMAIN>as a CloudflareArecord pointing atBACKEND_IP,proxied=false(skips if the record already exists — it does not update an existing record, to avoid clobbering something set up differently on purpose). - Write
/etc/traefik/dynamic/<subdomain>.ymlrouting that hostname tohttp://<backend-ip>:<host-port>, reusing the existing wildcard cert. This step needssudo(interactively — the script does not attempt to cache or supply a password).
Never commit the Cloudflare token anywhere. Pass it as an env var for a single invocation only.
- Create
-
Verify:
dig @1.1.1.1 <subdomain>.<BASE_DOMAIN> +short # should return BACKEND_IP curl -s -o /dev/null -w '%{http_code}\n' https://<subdomain>.<BASE_DOMAIN>/Traefik's
fileprovider watches/etc/traefik/dynamicand picks up new router files automatically — no restart needed.
Notes / gotchas
- If the service needs HTTPS all the way to the backend (rare — most backends
are plain HTTP internally), set
BACKEND_SCHEME=httpsbefore running the script. ROOT_DOMAIN,BASE_DOMAIN,CERT_MAIN_DOMAIN,CLOUDFLARE_API_TOKEN, andBACKEND_IPare all required — the script fails fast with a clear message if any is unset. There is no default or auto-detection forBACKEND_IP; you must resolve it yourself for whatever private network you're using (e.g.tailscale ip -4for Tailscale) and pass it in.- This only handles exposure (DNS + reverse proxy). It does not create the Docker Compose file, generate secrets, or start the service.
What ships with it: 1 file
4.5 KB alongside SKILL.md, 1 of them executable
scripts/
- expose.shruns4.5 KB
Gives 0 of the 12 instructions most containers cloud skills give in ~1.1k tokens
Counted across 607 of the 657 authors here whose files we hold, read 2026-08-07
- Run containers as a non-root userin 66 of 607, across 46 files
- Use multi-stage buildsin 53 of 607, across 44 files
- Use Promise.all for independent operationsin 47 of 607, across 13 files
- Import directly instead of barrel filesin 46 of 607, across 12 files
- Use ternary instead of AND for conditionalsin 45 of 607, across 12 files
- Use Set or Map for O(1) lookupsin 42 of 607, across 10 files
- Create a .dockerignore filein 41 of 607, across 31 files
- Read individual rule files for detailsin 39 of 607, across 9 files
- Copy dependency files before source codein 36 of 607, across 23 files
- Authenticate server actions like API routesin 35 of 607, across 7 files
- Use next/dynamic for heavy componentsin 34 of 607, across 9 files
- Use React.cache for per-request deduplicationin 34 of 607, across 10 files
Said here and by no other author read
- bind the service to the private IP
- verify the host port is available
- run the expose helper script
- resolve and provide the backend IP
- set backend scheme to https if required
- verify HTTP status code
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.