agentsclimarketplace

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

Install
npx -y skills add CervantesVive/agent-skills --skill expose-private-service

Assembled 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

  1. 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 -tlnp first to confirm <host-port> isn't already taken by another local service on this host.

  2. 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_IP can 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 Cloudflare A record pointing at BACKEND_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>.yml routing that hostname to http://<backend-ip>:<host-port>, reusing the existing wildcard cert. This step needs sudo (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.

  3. 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 file provider watches /etc/traefik/dynamic and 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=https before running the script.
  • ROOT_DOMAIN, BASE_DOMAIN, CERT_MAIN_DOMAIN, CLOUDFLARE_API_TOKEN, and BACKEND_IP are all required — the script fails fast with a clear message if any is unset. There is no default or auto-detection for BACKEND_IP; you must resolve it yourself for whatever private network you're using (e.g. tailscale ip -4 for 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/

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.

Keep looking

Skills are one crate of 326,834. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.