Construire un dashboard Nuxt 4 pour piloter Caddy via son Admin API — authentification JWT, reverse proxy CRUD, import/export, et déploiement Docker Compose.
Caddy expose une API REST complète pour lire et modifier sa configuration à chaud, sans redémarrage. C'est la fondation d'OpenCaddyHost.
Par défaut, Caddy écoute sur 127.0.0.1:2019 pour les requêtes d'administration. Toute modification de configuration passe par cette API — y compris ce que fait la CLI caddy reload.
Endpoints principaux :
| Méthode | Endpoint | Rôle |
|---|---|---|
GET | /config/ | Lire la configuration complète |
POST | /load | Remplacer toute la configuration |
PATCH | /config/apps/http/servers | Modifier une section spécifique |
GET | /id/{id} | Accéder à un élément par son @id |
DELETE | /id/{id} | Supprimer un élément par son @id |
Un reverse proxy Caddy en JSON ressemble à ça :
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":443"],
"routes": [
{
"@id": "proxy-mon-app",
"match": [{ "host": ["mon-app.example.com"] }],
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": "localhost:8080" }]
}
]
}
]
}
}
}
}
}Le champ @id est crucial : il permet d'accéder à une route précise via /id/proxy-mon-app pour la modifier ou la supprimer sans toucher au reste de la config.
curl http://127.0.0.1:2019/config/ | jq .Sur une instance fraîche, la réponse est null — Caddy n'a aucune config chargée.
curl -X POST http://127.0.0.1:2019/load \
-H "Content-Type: application/json" \
-d '{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":80"],
"routes": [
{
"@id": "proxy-test",
"match": [{ "host": ["test.localhost"] }],
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": "localhost:3000" }]
}
]
}
]
}
}
}
}
}'Réponse : HTTP 200 sans corps = configuration chargée. Le proxy est actif immédiatement.
curl -X DELETE http://127.0.0.1:2019/id/proxy-testUn seul appel. Caddy retire la route de la configuration active sans toucher aux autres proxies.
Caddy gère Let's Encrypt automatiquement. Changer le port :80 en :443 dans la config JSON suffit :
{
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": "localhost:8080" }]
}
],
"match": [{ "host": ["mon-app.example.com"] }],
"terminal": true
}Caddy détecte le nom de domaine, contacte Let's Encrypt, et obtient le certificat. Aucune config SSL à écrire.
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": "localhost:8080" }],
"headers": {
"request": {
"set": {
"X-Real-IP": ["{http.request.remote.host}"],
"X-Forwarded-Proto": ["https"]
}
}
}
}{http.request.remote.host} est une variable de template Caddy — elle est résolue au moment de la requête, pas à la configuration.
Par défaut, l'Admin API est sur 127.0.0.1:2019 — accessible uniquement en local. Dans le docker-compose.yml d'OpenCaddyHost, elle est remappée sur 2020 pour éviter les conflits :
caddy:
ports:
- "2020:2019" # Admin API exposée sur le port 2020 de l'hôteCADDY_ADMIN_URL=http://caddy:2019 dans le conteneur dashboard — les deux services parlent via le réseau Docker interne, pas via l'hôte.
curl http://127.0.0.1:2019/config/
# 200 → API active
# Connection refused → Caddy arrêté ou Admin API désactivéeOpenCaddyHost expose un endpoint /api/caddy/health qui fait cette vérification et la renvoie au dashboard.