Construire un dashboard Nuxt 4 pour piloter Caddy via son Admin API — authentification JWT, reverse proxy CRUD, import/export, et déploiement Docker Compose.
OpenCaddyHost se déploie avec un seul docker compose up qui lance Caddy et le dashboard ensemble, sur le même réseau Docker.
version: '3.8'
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "2020:2019" # Admin API exposée sur le port 2020 de l'hôte
volumes:
- caddy_config:/config
- caddy_data:/data
networks:
- caddy_network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:2019/config/"]
interval: 30s
timeout: 10s
retries: 3
dashboard:
build: .
restart: unless-stopped
ports:
- "3000:3000"
environment:
- JWT_SECRET=${JWT_SECRET}
- ADMIN_USERNAME=${ADMIN_USERNAME}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- CADDY_ADMIN_URL=http://caddy:2019 # réseau Docker interne
- NODE_ENV=production
depends_on:
caddy:
condition: service_healthy # attend que Caddy soit prêt
volumes:
- ./logs:/app/logs
networks:
- caddy_network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
start_period: 60s
interval: 30s
volumes:
caddy_config:
caddy_data:
networks:
caddy_network:
driver: bridgeCADDY_ADMIN_URL=http://caddy:2019 — le dashboard parle à Caddy via le nom de service Docker (caddy), pas via localhost. Sur le réseau bridge Docker, chaque service est résolvable par son nom.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.output ./output
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
CMD ["node", "output/server/index.mjs"]Build multi-étapes : l'image finale ne contient que .output/ — pas de node_modules, pas de sources. Image finale ~150MB au lieu de ~800MB.
# Copier et configurer l'environnement
cp .env.example .env
nano .env # changer JWT_SECRET, ADMIN_PASSWORD
# Build et démarrage
docker compose up -d --build
# Vérifier que les deux services tournent
docker compose ps
# Logs en temps réel
docker compose logs -f dashboardNAME STATUS
caddy Up (healthy)
dashboard Up (healthy)# Dashboard accessible
curl http://localhost:3000/api/health
# Admin API Caddy accessible depuis l'hôte
curl http://localhost:2020/config/
# Admin API depuis le conteneur dashboard (via réseau Docker)
docker compose exec dashboard wget -qO- http://caddy:2019/config/1. Ne pas exposer le port 2020 publiquement
Le port 2020 (Admin API Caddy) ne doit être accessible qu'en local ou via VPN. Sur un VPS, bloquer ce port avec UFW :
ufw deny 2020Et retirer le mapping de port dans docker-compose.yml si le dashboard et Caddy sont sur la même machine :
caddy:
# ports:
# - "2020:2019" ← supprimer en production si non nécessaire2. Caddy devant le dashboard
En production, on place Caddy comme reverse proxy devant le dashboard lui-même. Le dashboard répond en HTTPS automatiquement, Caddy gère Let's Encrypt :
dashboard.example.com {
reverse_proxy localhost:3000
}Mais puisque la config Caddy est gérée via l'Admin API JSON, l'équivalent est d'envoyer cette route au démarrage :
curl -X POST http://localhost:2020/load \
-H "Content-Type: application/json" \
-d '{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":443"],
"routes": [{
"@id": "proxy-dashboard",
"match": [{"host": ["dashboard.example.com"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "localhost:3000"}]}]
}]
}
}
}
}
}'3. Changer les credentials par défaut
JWT_SECRET=$(openssl rand -base64 48)
ADMIN_USERNAME=mon-username
ADMIN_PASSWORD=mot-de-passe-fort-et-uniqueNe jamais pousser .env sur GitHub — il est dans .gitignore par défaut.
git pull
docker compose up -d --build dashboard # rebuild uniquement le dashboard, Caddy reste intact--build dashboard rebuild seulement le service dashboard. Caddy continue de tourner sans interruption — les proxies configurés restent actifs.
La config Caddy est persistée dans le volume caddy_config. Pour la sauvegarder :
# Export via l'API
curl http://localhost:2020/config/ > caddy-backup-$(date +%Y%m%d).json
# Restore
curl -X POST http://localhost:2020/load \
-H "Content-Type: application/json" \
-d @caddy-backup-20260616.jsonOu directement via le dashboard : page "Import / Export" → bouton "Télécharger".