Replace Cloudflare DNS challenge with standard Let's Encrypt HTTP-01

Caddy's built-in ACME support handles TLS automatically — no CF_API_TOKEN,
no Cloudflare account, no DNS plugin needed. Requires ports 80+443 forwarded
to the Pi and ACME_EMAIL set in infra/.env.
This commit is contained in:
Claude 2026-03-20 11:41:40 +00:00
parent 3794f4cf36
commit dc59293c5e
No known key found for this signature in database
4 changed files with 46 additions and 83 deletions

View file

@ -140,29 +140,24 @@ docker run --rm hello-world # verify
--- ---
## 6. Domain and DNS (Cloudflare) ## 6. Domain and DNS
You need a domain whose DNS is managed by Cloudflare (the free plan is fine). You need a domain pointing to your Pi's public IP. Any DNS provider works.
1. In Cloudflare, add two DNS records pointing to your **public home IP**: 1. Add DNS A records pointing to your **public home IP**:
| Type | Name | Content | Proxy | | Type | Name | Content |
|---|---|---|---| |---|---|---|
| A | `hiy` | `<your-public-IP>` | Proxied (orange cloud) | | A | `hiy` | `<your-public-IP>` |
| A | `*` | `<your-public-IP>` | Proxied (orange cloud) | | A | `*` | `<your-public-IP>` |
The wildcard `*` record routes `<appname>.yourdomain.com` to the Pi. The wildcard `*` record routes `<appname>.yourdomain.com` to the Pi.
2. In **Cloudflare → SSL/TLS → Overview**, set mode to **Full (strict)**.
3. Create a **Cloudflare API token** for Caddy's ACME DNS challenge:
- Go to My Profile → API Tokens → Create Token
- Use the **Edit zone DNS** template, scope it to your zone
- Copy the token — you will need it in step 8
### Port forwarding ### Port forwarding
Forward the following ports on your router to the Pi's static IP: Forward the following ports on your router to the Pi's static IP.
**Both ports are required** — Caddy uses port 80 to prove domain ownership
to Let's Encrypt before issuing the HTTPS certificate.
| External | Internal | | External | Internal |
|---|---| |---|---|
@ -195,63 +190,20 @@ Minimum required variables:
# Your domain (apps will be at <name>.yourdomain.com) # Your domain (apps will be at <name>.yourdomain.com)
DOMAIN_SUFFIX=yourdomain.com DOMAIN_SUFFIX=yourdomain.com
# Cloudflare API token for ACME DNS-01 challenge # Email for Let's Encrypt expiry notices
CF_API_TOKEN=your_cloudflare_api_token ACME_EMAIL=you@example.com
``` ```
--- ---
## 9. Caddy — automatic HTTPS ## 9. Caddy — automatic HTTPS
Caddy handles TLS via Cloudflare's DNS challenge (no port-80 HTTP challenge Caddy obtains a Let's Encrypt certificate automatically via the HTTP-01
needed, works even behind CGNAT). challenge. No DNS API token or Cloudflare account required.
Edit `proxy/caddy.json` and replace the top-level config with: The `proxy/Caddyfile` is already configured — nothing to edit here.
Just make sure `DOMAIN_SUFFIX` and `ACME_EMAIL` are set in `infra/.env`
```json and that ports 80 and 443 are forwarded to the Pi (see step 6).
{
"admin": { "listen": "0.0.0.0:2019" },
"apps": {
"tls": {
"automation": {
"policies": [{
"subjects": ["*.yourdomain.com", "yourdomain.com"],
"issuers": [{
"module": "acme",
"challenges": {
"dns": {
"provider": {
"name": "cloudflare",
"api_token": "{env.CF_API_TOKEN}"
}
}
}
}]
}]
}
},
"http": {
"servers": {
"hiy": {
"listen": [":80", ":443"],
"routes": [
{
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "server:3000"}]
}]
}
]
}
}
}
}
}
```
> **Note:** The Caddy image in `docker-compose.yml` needs the Cloudflare DNS
> plugin. Use `caddy:2-alpine` with `xcaddy` or replace the image with
> `ghcr.io/caddybuilds/caddy-cloudflare:latest`.
--- ---
@ -427,7 +379,7 @@ Caddy never restarts.
| Symptom | Likely cause | Fix | | Symptom | Likely cause | Fix |
|---|---|---| |---|---|---|
| Dashboard not reachable | Compose not started / port 443 not forwarded | `docker compose logs server` | | Dashboard not reachable | Compose not started / port 443 not forwarded | `docker compose logs server` |
| TLS certificate error | CF_API_TOKEN wrong or DNS not propagated | Check Caddy logs: `docker compose logs caddy` | | TLS certificate error | Port 80/443 not forwarded or DNS not propagated yet | Check Caddy logs: `docker compose logs caddy` |
| Build fails — "docker not found" | Docker socket not mounted | Verify `docker-proxy` service is up | | Build fails — "docker not found" | Docker socket not mounted | Verify `docker-proxy` service is up |
| Build fails — "pack not found" | `pack` not installed | See step 11 | | Build fails — "pack not found" | `pack` not installed | See step 11 |
| Webhook returns 401 | Secret mismatch | Regenerate app, re-copy secret to GitHub | | Webhook returns 401 | Secret mismatch | Regenerate app, re-copy secret to GitHub |

5
infra/.env.example Normal file
View file

@ -0,0 +1,5 @@
# Your domain — apps will be served at <name>.yourdomain.com
DOMAIN_SUFFIX=yourdomain.com
# Email address for Let's Encrypt expiry notices (required for HTTPS)
ACME_EMAIL=you@example.com

View file

@ -53,11 +53,14 @@ services:
- "80:80" - "80:80"
- "443:443" - "443:443"
- "2019:2019" # admin API - "2019:2019" # admin API
environment:
DOMAIN_SUFFIX: ${DOMAIN_SUFFIX:-localhost}
ACME_EMAIL: ${ACME_EMAIL:-}
volumes: volumes:
- ../proxy/caddy.json:/etc/caddy/caddy.json:ro - ../proxy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data - caddy-data:/data
- caddy-config:/config - caddy-config:/config
command: caddy run --config /etc/caddy/caddy.json command: caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
networks: networks:
- hiy-net - hiy-net
- default - default

View file

@ -1,29 +1,32 @@
# HIY — Caddyfile # HIY — Caddyfile
# #
# Local development: auto-HTTPS is disabled; everything runs on :80. # Caddy automatically obtains a Let's Encrypt certificate for every domain it
# Production (Pi): remove the `auto_https off` line, set your real email, # serves (HTTP-01 challenge). No Cloudflare or DNS API token required.
# and Caddy will obtain Let's Encrypt certificates automatically.
# #
# Wildcard TLS on the Pi (recommended): # Requirements:
# tls your@email.com { # - Ports 80 and 443 must be publicly reachable (router port-forward to Pi)
# dns cloudflare {env.CLOUDFLARE_API_TOKEN} # - DNS A record for {$DOMAIN_SUFFIX} must point to your public IP
# } # - Set ACME_EMAIL in infra/.env (Let's Encrypt needs a contact address)
#
# Local dev: set DOMAIN_SUFFIX=localhost in infra/.env — Caddy will use a
# self-signed cert automatically for localhost.
{ {
# Admin API — used by build.sh to update routes dynamically. # Admin API — used by hiy-server to add/remove app routes dynamically.
admin 0.0.0.0:2019 admin 0.0.0.0:2019
# Comment this out on the Pi with a real domain. # Contact email for Let's Encrypt expiry notices.
auto_https off email {$ACME_EMAIL}
} }
# Fallback: serves the HIY dashboard itself. # HIY dashboard — served at your root domain.
:80 { {$DOMAIN_SUFFIX} {
reverse_proxy server:3000 reverse_proxy server:3000
} }
# Example of what the build script adds via the Caddy API: # Deployed apps are added here dynamically by hiy-server via the Caddy API.
# Each entry looks like:
# #
# myapp.yourdomain.com { # myapp.{$DOMAIN_SUFFIX} {
# reverse_proxy <container-ip>:3000 # reverse_proxy <container-ip>:<port>
# } # }