Files
serv_benchmark/docs/SESSION_2025-12-31_DOCKER_IMAGES_FIX.md
Gilles Soulier c67befc549 addon
2026-01-05 16:08:01 +01:00

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 /uploads et config nginx
  • frontend/config.js - Ajout uploadsPath
  • 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