Skip to content
Back to skills

Domain Skill

ASecurity

Architect production-ready domain routing. Configure reverse proxies (e.g., Traefik), SSL/TLS certificates, and Cloudflare tunnels for secure public access.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 3, 2026
ai-agentsrustgobashnodeexpressdockerapifrontendbackendsecurity

Works with

  • api

Security analysis

A96/100
  • mediumUses curl or wget to download content

Pro shows the line behind each finding and how to fix it

Scanned June 3, 2026

npx -y skills add kinqsradiollc/BrainRouter --skill domain-skill --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Domain Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Domain Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kinqsradiollc-domain-skill/badge)](https://www.skillsdirectory.com/skills/kinqsradiollc-domain-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: domain-skill
description: Architect production-ready domain routing. Configure reverse proxies (e.g., Traefik), SSL/TLS certificates, and Cloudflare tunnels for secure public access.
hints: |
  - Always identify the primary hostnames (e.g. app.domain.com, api.domain.com) before structuring routing.
  - Review Traefik labels in docker-compose.yml to ensure correct entrypoint and rule mappings.
  - Use Cloudflare Zero Trust tunnels to establish secure outbound-only ingress paths without exposing local ports.
  - Set up environment variables (like DOMAIN, CORS_ORIGIN, and WebAuthn relying party IDs) dynamically.
  - Test routing and certificate issuance sequentially, beginning with Flexible TLS before strict enforcement.
---

# Custom Domain & Networking

## Overview

A robust routing architecture ensures secure, encrypted public access to backend and frontend services. This skill guides the setup of production-ready routing using reverse proxies (e.g., Traefik v3) coupled with secure edge ingress (e.g., Cloudflare Tunnels) to direct external traffic cleanly to containerized services without exposing local ports.

## Ingress Architecture

```
Browser
  │  HTTPS (TLS terminated by Cloudflare)
  ▼
Cloudflare Edge (api.yourdomain.com)
  │  Outbound tunnel (no open ports needed)
  ▼
cloudflared daemon  ← Docker container, profile: production
  │  HTTP  http://traefik:80
  ▼
Traefik v3          ← traefik container
  │  HTTP  :3001
  ▼
Node.js / Express   ← backend container
```

> Local dev skips cloudflared entirely. Traefik routes `Host(\`localhost\`)` directly.

---

## Environment Variables

Ensure the following networking environment variables are configured in the `.env` or deployment settings:

| Variable | Dev value | Prod value | Purpose |
|---|---|---|---|
| `DOMAIN` | `api.yourdomain.com` | `api.yourdomain.com` | Traefik HTTPS router host rule |
| `ACME_EMAIL` | `admin@yourdomain.com` | `admin@yourdomain.com` | Let's Encrypt certificate contact |
| `CLOUDFLARE_TUNNEL_TOKEN` | *(blank)* | `<token from CF dashboard>` | Authenticates cloudflared to Cloudflare |
| `FRONTEND_URL` | `http://localhost:3000` | `https://yourdomain.com` | Email link base URL |
| `CORS_ORIGIN` | `http://localhost:3000,https://yourdomain.com` | `https://yourdomain.com` | Allowed CORS origins |
| `PASSKEY_RP_ID` | `localhost` | `yourdomain.com` | WebAuthn Relying Party ID |
| `PASSKEY_ORIGIN` | `http://localhost:3000` | `https://yourdomain.com` | WebAuthn allowed origin |

---

## Traefik Router Setup

Two routers are typically configured in `docker-compose.yml` on the `backend` service:

### HTTP Router (always active)

```yaml
- "traefik.http.routers.backend.rule=Host(`localhost`) || Host(`api.yourdomain.com`)"
- "traefik.http.routers.backend.entrypoints=web"
```

Accepts both `localhost` (dev) and `api.yourdomain.com` (prod via cloudflared HTTP passthrough).

### HTTPS Router (activates with Cloudflare Full strict)

```yaml
- "traefik.http.routers.backend-secure.rule=Host(`api.yourdomain.com`)"
- "traefik.http.routers.backend-secure.entrypoints=websecure"
- "traefik.http.routers.backend-secure.tls=true"
- "traefik.http.routers.backend-secure.tls.certresolver=letsencrypt"
```

Traefik will auto-issue a Let's Encrypt cert once `api.yourdomain.com` is publicly resolvable.

---

## Cloudflare SSL Mode Decision

| Mode | When to use | Traefik TLS needed? |
|---|---|---|
| **Flexible** | Dev/staging; simplest setup | ❌ No |
| **Full** | Traefik has any cert (even self-signed) | ✅ Yes |
| **Full (strict)** | Production; Traefik has valid Let's Encrypt cert | ✅ Yes |

> **Recommended:** Start with **Flexible** to verify connectivity, then switch to **Full (strict)** once the domain is proven reachable and Let's Encrypt issues the cert.

---

## Workflow

### Step 1 — Create the tunnel

1. Go to [Cloudflare Zero Trust](https://one.dash.cloudflare.com) → **Networks → Tunnels → Create a tunnel**
2. Choose **Cloudflared** connector
3. Name the tunnel `app-tunnel`
4. Copy the **tunnel token** — this is your `CLOUDFLARE_TUNNEL_TOKEN`

### Step 2 — Configure Public Hostname

In the tunnel settings, add a Public Hostname:

| Field | Value |
|---|---|
| Subdomain | `api` |
| Domain | `yourdomain.com` |
| Service Type | `HTTP` |
| Service URL | `traefik:80` |

Cloudflare will automatically create the DNS record:

| Type | Name | Target | Proxy |
|---|---|---|---|
| CNAME | `api` | `<tunnel-id>.cfargotunnel.com` | ✅ Proxied |

### Step 3 — Add token to `.env`

```bash
# .env
CLOUDFLARE_TUNNEL_TOKEN=<paste token here>
```

### Step 4 — Start cloudflared

```bash
# Only starts services with the production profile
docker compose --profile production up -d cloudflared

# Verify connection
docker logs cloudflared --tail 20
# Expected: "Connection established" / "Registered tunnel connection"
```

### Step 5 — Verify

```bash
# Health check through the full tunnel
curl https://api.yourdomain.com/health
# Expected: { "status": "ok", ... }

# Check Traefik picked up the new router
open http://localhost:8080   # Traefik dashboard
```

---

## SSL Mode: Switch to Full (strict)

Once Let's Encrypt issues the cert (check Traefik dashboard → TLS), switch Cloudflare:

1. Cloudflare dashboard → **SSL/TLS → Overview**
2. Change mode to **Full (strict)**
3. No Docker restart needed — Traefik handles this automatically

---

## Cloudflared Compose Profile

The `cloudflared` service uses `profiles: [production]` so it **never starts** during normal dev:

```bash
# Dev (cloudflared excluded)
docker compose up -d

# Production (all services including cloudflared)
docker compose --profile production up -d

# Start cloudflared only (if stack is already running)
docker compose --profile production up -d cloudflared
```

---

## Migration Path: Tunnel → VPS + WireGuard

When you move to a dedicated server with a static IP:

1. **Remove** `cloudflared` service from docker-compose (or keep as fallback)
2. **Point** Cloudflare DNS `A` record `api` → `<vps-ip>` (Proxied ✅)
3. **Open** ports `80` and `443` on the VPS firewall
4. **Traefik** will handle Let's Encrypt directly via HTTP challenge on port 80
5. **Update** SSL/TLS mode to **Full (strict)** in Cloudflare

The Traefik labels and ACME config in docker-compose remain identical — only the ingress path changes.

---

## Pitfalls

| Problem | Cause | Fix |
|---|---|---|
| `curl: (6) Could not resolve host` | DNS not propagated | Wait 1–5 min after Cloudflare saves the CNAME |
| Tunnel connected but 502 | cloudflared can't reach `traefik:80` | Verify both are on the same docker network; check `docker network inspect` |
| Let's Encrypt cert not issued | Domain not publicly reachable on port 80 | Use Flexible SSL mode until tunnel is confirmed working |
| CORS errors in browser | `CORS_ORIGIN` missing origin domain | Add proper origin to `.env` and restart backend |
| WebAuthn fails on prod | `PASSKEY_RP_ID` still set to `localhost` | Update to the production domain and redeploy |
| `--profile production` not recognized | Old Docker Compose version | Upgrade to Docker Compose v2.x (`docker compose version`) |

## When to Use

- Mapping custom domains (e.g., `app.domain.com` or `api.domain.com`) to containerized services.
- Designing secure ingress architectures via reverse proxies (e.g., Traefik, Nginx) and edge providers (e.g., Cloudflare).
- Setting up SSL/TLS termination, automated certificate resolvers (Let's Encrypt), or Cloudflare Tunnels (`cloudflared`).

**When NOT to use:**
- Basic local-only dev environments that do not require DNS mapping or secure public access.
- Deploying on serverless platforms (e.g., Vercel, Netlify) where domain routing is fully managed by the provider.

## Common Rationalizations

| Rationalization | Reality |
|---|---|
| "I will expose the container port directly to the internet." | Exposing service ports directly bypasses reverse proxy benefits (load balancing, SSL termination, request filtering) and introduces massive security vulnerabilities. |
| "I'll use HTTP in staging to save setup time." | Staging must mimic production. Omitting TLS in staging leads to hidden CORS, WebAuthn, cookie security, or certificate routing errors that only show up in production. |
| "Setting up Cloudflare tunnels is too slow; I'll just open port 80/443." | Opening incoming ports makes the host machine an active target for automated port scanners. Outbound-only tunnels are vastly more secure. |

## Red Flags

- Hardcoded domain names in docker-compose configurations instead of environment variables.
- Using Flexible SSL/TLS modes in production indefinitely without terminating TLS at the reverse proxy (leaves edge-to-origin traffic unencrypted).
- Mixing production tunnel credentials or domain records directly inside shared development files.
- Exposing Traefik dashboards publicly without authentication or strong basic auth middlewares.

## Verification

After completing the domain setup, verify:
- [ ] Ingress paths are tested and return valid HTTP statuses (e.g., 200 OK, secure 301 redirects).
- [ ] SSL/TLS certificate is verified as valid and issued by a recognized CA (e.g., Let's Encrypt, Cloudflare Edge).
- [ ] CORS origins and authentication parameters (e.g., WebAuthn/Passkey RP IDs) match the active host exactly.
- [ ] Cloudflared connections are confirmed in container logs with `Connection established` and no active errors.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.