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.

2 things to look at

  • 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 705 authors here whose files we hold, read 2026-09-06

  • Run as non-root userin 34 of 607, across 27 files
  • Use multi-stage buildsin 29 of 607
  • Set resource requests and limitsin 24 of 607, across 20 files
  • Configure liveness and readiness probesin 18 of 607, across 14 files
  • Use named volumes for persistent datain 14 of 607, across 9 files
  • Pin base image versionsin 14 of 607
  • Set up environment variablesin 14 of 607, across 10 files
  • Pin provider versionsin 14 of 607
  • Apply least privilege RBAC permissionsin 10 of 607, across 7 files
  • Create a dockerignore filein 10 of 607
  • Use remote state with lockingin 9 of 607
  • Pin base images by digestin 9 of 607, across 8 files

Said here and by no other author read

  • Bind the service to the private IP
  • Check ports using ss before binding
  • Run the helper script
  • Verify the DNS record and HTTPS response

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 325,949. 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.