7.0 KiB
Executable File
Session 2025-12-31 : Correction Docker - Servir les images
🎯 Problème
Les images uploadées dans le module périphériques n'étaient pas accessibles depuis le frontend.
Erreurs :
GET http://10.0.0.50:8087/app/uploads/peripherals/photos/3/csfingerprint_20251231_092242.webp
[HTTP/1.1 404 Not Found]
🔍 Analyse
Problème 1 : Montage de volume impossible
Tentative initiale de monter ./uploads vers /usr/share/nginx/html/app/uploads dans le conteneur nginx.
Erreur Docker :
error mounting "/home/gilles/projects/serv_benchmark/uploads" to rootfs at "/usr/share/nginx/html/app/uploads":
mkdirat /var/lib/docker/rootfs/overlayfs/.../usr/share/nginx/html/app: read-only file system
Cause : Le système de fichiers root du conteneur nginx:alpine est en lecture seule. Docker ne peut pas créer le répertoire intermédiaire /app/ dans /usr/share/nginx/html/.
Problème 2 : Chemin filesystem vs chemin web
- Base de données : Stocke les chemins filesystem du backend :
/app/uploads/peripherals/photos/3/image.webp - Frontend : A besoin de chemins web accessibles via nginx :
/uploads/peripherals/photos/3/image.webp
✅ Solutions implémentées
1. Montage simplifié des uploads
Fichier : docker-compose.yml (ligne 37)
volumes:
- ./uploads:/uploads:ro
✅ Montage direct vers /uploads (pas de répertoire intermédiaire à créer)
2. Configuration nginx personnalisée
Fichier créé : frontend/nginx.conf
server {
listen 80;
server_name localhost;
# Serve static frontend files
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ =404;
}
# Serve uploaded files
location /uploads/ {
alias /uploads/;
autoindex off;
# Cache uploaded images for 1 day
expires 1d;
add_header Cache-Control "public, immutable";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
Fonctionnalités :
- ✅ Location
/uploads/sert les fichiers depuis/uploads/dans le conteneur - ✅ Cache navigateur 1 jour pour les images (performance)
- ✅ En-têtes de sécurité (XSS, Clickjacking, MIME sniffing)
Montage dans Docker : docker-compose.yml (ligne 35)
volumes:
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
3. Conversion des chemins dans l'API backend
Fichier : backend/app/api/endpoints/peripherals.py
Endpoint /peripherals/{id}/photos (lignes 329-355)
@router.get("/{peripheral_id}/photos")
def get_photos(
peripheral_id: int,
db: Session = Depends(get_peripherals_db)
):
"""Get all photos for a peripheral"""
photos = db.query(PeripheralPhoto).filter(
PeripheralPhoto.peripheral_id == peripheral_id
).all()
# Convert stored paths to web-accessible URLs
result = []
for photo in photos:
photo_dict = {
"id": photo.id,
"peripheral_id": photo.peripheral_id,
"filename": photo.filename,
"stored_path": photo.stored_path.replace('/app/uploads/', '/uploads/')
if photo.stored_path.startswith('/app/uploads/')
else photo.stored_path,
"mime_type": photo.mime_type,
"size_bytes": photo.size_bytes,
"description": photo.description,
"is_primary": photo.is_primary,
"uploaded_at": photo.uploaded_at
}
result.append(photo_dict)
return result
Transformation :
- Base de données :
/app/uploads/peripherals/photos/3/image.webp - API retourne :
/uploads/peripherals/photos/3/image.webp
Endpoint /peripherals/{id}/documents (lignes 425-450)
Même transformation pour les documents :
stored_path": doc.stored_path.replace('/app/uploads/', '/uploads/')
if doc.stored_path.startswith('/app/uploads/')
else doc.stored_path
4. Configuration frontend
Fichier : frontend/config.js (lignes 29-31)
if (!window.BenchConfig.uploadsPath) {
window.BenchConfig.uploadsPath = '/uploads';
}
Permet de centraliser la configuration du chemin des uploads si besoin de le modifier.
📊 Flux complet
1. Upload photo
└─> Backend stocke : /app/uploads/peripherals/photos/3/image.webp (filesystem)
2. Frontend demande : GET /api/peripherals/3/photos
└─> Backend convertit : /app/uploads/... → /uploads/...
└─> API retourne : /uploads/peripherals/photos/3/image.webp
3. Frontend affiche : <img src="/uploads/peripherals/photos/3/image.webp">
└─> Nginx sert depuis : /uploads/ (monté depuis ./uploads)
└─> HTTP 200 OK
🧪 Tests de validation
Test 1 : Fichier existe dans le conteneur
docker exec linux_benchtools_frontend ls -la /uploads/peripherals/photos/3/
Résultat :
-rwxrwxrwx 1 root root 20084 Dec 31 09:22 csfingerprint_20251231_092242.webp
✅ OK
Test 2 : API retourne le bon chemin
curl http://10.0.0.50:8007/api/peripherals/3/photos
Résultat :
{
"stored_path": "/uploads/peripherals/photos/3/csfingerprint_20251231_092242.webp"
}
✅ OK
Test 3 : Nginx sert l'image
curl -I http://10.0.0.50:8087/uploads/peripherals/photos/3/csfingerprint_20251231_092242.webp
Résultat :
HTTP/1.1 200 OK
Content-Type: image/webp
Content-Length: 20084
Cache-Control: max-age=86400
Cache-Control: public, immutable
✅ OK
Test 4 : Frontend accessible
curl -I http://10.0.0.50:8087/peripherals.html
Résultat :
HTTP/1.1 200 OK
Content-Type: text/html
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
✅ OK
📁 Fichiers modifiés
Créés
- ✅
frontend/nginx.conf- Configuration nginx personnalisée - ✅
docs/SESSION_2025-12-31_DOCKER_IMAGES_FIX.md- Cette documentation
Modifiés
- ✅
docker-compose.yml- Montage/uploadset config nginx - ✅
frontend/config.js- AjoutuploadsPath - ✅
backend/app/api/endpoints/peripherals.py- Conversion chemins dans API - ✅
backend/app/schemas/peripheral.py- Suppression tentative @property (non retenue)
🔄 Commandes de déploiement
# Rebuild backend avec nouvelles routes API
docker compose up -d --build backend
# Recréer frontend avec nginx.conf
docker compose up -d frontend
# Vérifier tous les conteneurs
docker compose ps
🎯 Améliorations futures possibles
- Ajouter compression gzip pour les images dans nginx
- Implémenter un CDN ou proxy cache pour les uploads
- Ajouter authentification pour certains uploads sensibles
- Lazy loading des images dans le frontend
- WebP avec fallback JPEG pour compatibilité navigateurs anciens
Date : 31 décembre 2025 Statut : ✅ Résolu et testé Impact : Les images des périphériques sont maintenant accessibles depuis le frontend