12 KiB
Image Upload & Loading Fix - Brand Master
Problem Summary
Images were not loading correctly in the Brand Master application due to:
- URL Resolution Issue: Images stored with relative paths (
/uploads/products/...) were resolving to the frontend domain instead of the backend - Missing Category Upload Endpoint: No dedicated endpoint for category image uploads
- Ingress Routing: No route from frontend domain to backend for serving static uploads
- No Fallback Handling: Broken images displayed when URLs were invalid
Solutions Implemented
1. Backend Changes
a. Updated Settings Configuration
File: backend/app/config.py
- Added
backend_urlsetting to store the backend URL - This is set via
BACKEND_URLenvironment variable in Kubernetes
backend_url: str = "http://localhost:8000"
b. Enhanced Upload Utility
File: backend/app/utils.py
- Modified
save_upload_file()to acceptbackend_urlparameter - Now returns full URLs (e.g.,
https://api-brand-master.dvirlabs.com/uploads/products/...) instead of relative paths - Maintains backward compatibility with relative paths if
backend_urlnot provided
def save_upload_file(upload_file, folder: str = "products", backend_url: str = None) -> str:
# ... saves file ...
relative_path = f"/uploads/{folder}/{unique_filename}"
if backend_url:
backend_url = backend_url.rstrip('/')
return f"{backend_url}{relative_path}"
return relative_path
c. Updated Product Upload Endpoints
File: backend/app/routers/products.py
- Updated
/api/products/upload-imageto usebackend_urlfrom settings - Updated
/api/products/upload-imagesto usebackend_urlfrom settings - Images now return full URLs immediately
d. Added Category Upload Endpoint
File: backend/app/routers/categories.py
- Added new endpoint:
POST /api/categories/upload-image - Validates image file types (jpeg, png, jpg, webp, gif)
- Returns full URL with backend domain
- Saves to
uploads/categories/folder
2. Frontend Changes
a. Updated Admin Panel Image Upload
File: frontend/src/pages/Admin.jsx
Product Images:
- Removed URL building logic (backend returns full URLs)
- Simplified
handleImageUpload()function - Backend now handles URL generation
Category Images:
- Changed from
/products/upload-imageto/categories/upload-image - Removed URL building logic
- Directly uses backend-returned full URLs
b. Added Fallback Image Handling
File: frontend/src/components/ProductCard.jsx
- Added error state tracking with
useState - Handles image load failures with
onErrorhandler - Falls back to placeholder image if product image fails to load
- Fallback:
https://via.placeholder.com/400x400?text={product-name}
File: frontend/src/components/CategoryCard.jsx
- Added error state tracking with
useState - Handles image load failures with
onErrorhandler - Falls back to placeholder image if category image fails to load
- Fallback:
https://via.placeholder.com/300x300?text={category-name}
3. Kubernetes/Helm Changes
a. Updated Frontend Ingress
File: charts/brand-master-chart/templates/frontend-ingress.yaml
- Added
/uploadspath routing to backend service - This provides backward compatibility for any existing relative URLs
- Routes
/uploads/*requests from frontend domain to backend service - Ensures images work even if old relative URLs exist in database
Path Priority:
/uploads→ Backend Service (for images)/→ Frontend Service (for everything else)
b. Persistence Configuration
Already Configured in manifests/brand-master/values.yaml:
- PersistentVolumeClaim enabled for backend
- Storage: 15Gi on NFS storage class
- Mount path:
/app/uploads - Ensures images persist across pod restarts
Deployment Instructions
1. Backend Deployment
cd ~/OneDrive/Desktop/gitea/brand-master
# Build new backend image
docker build -t harbor.dvirlabs.com/my-apps/brand-master-backend:latest -f backend/Dockerfile backend/
# Push to Harbor
docker push harbor.dvirlabs.com/my-apps/brand-master-backend:latest
2. Frontend Deployment
cd ~/OneDrive/Desktop/gitea/brand-master
# Build new frontend image
docker build -t harbor.dvirlabs.com/my-apps/brand-master-frontend:latest -f frontend/Dockerfile frontend/
# Push to Harbor
docker push harbor.dvirlabs.com/my-apps/brand-master-frontend:latest
3. Update Helm Deployment
cd ~/OneDrive/Desktop/gitea/my-apps
# Update image tags in values.yaml if using specific tags
# Or use :latest for auto-pull
# Upgrade Helm release
helm upgrade brand-master charts/brand-master-chart \
-f manifests/brand-master/values.yaml \
-n my-apps
4. Verify Deployment
# Check pods are running
kubectl get pods -n my-apps -l app.kubernetes.io/name=brand-master
# Check backend logs
kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=50
# Check frontend logs
kubectl logs -n my-apps -l app.kubernetes.io/component=frontend --tail=50
# Verify PVC is mounted
kubectl describe pod -n my-apps -l app.kubernetes.io/component=backend | grep uploads
Testing Instructions
1. Test Category Image Upload
- Login as admin: https://brand-master.dvirlabs.com/admin
- Go to "Categories" tab
- Create or edit a category
- Upload an image
- Expected: Image preview shows immediately
- Save category
- Expected: Image displays on home page category cards
- Refresh page
- Expected: Image still loads correctly
2. Test Product Image Upload
- Login as admin
- Go to "Products" tab
- Create or edit a product
- Upload one or more images
- Expected: Image previews show immediately in upload section
- Save product
- Expected: Product displays correctly in products list
- View product detail page
- Expected: All images load and gallery works
- Refresh page
- Expected: All images still load correctly
3. Test Image Persistence
- Upload some product/category images
- Restart backend pod:
kubectl rollout restart deployment -n my-apps brand-master-backend - Wait for pod to be ready
- Expected: All previously uploaded images still load correctly
4. Test Fallback Handling
- Manually break an image URL in the database (or edit in admin with invalid URL)
- View product/category with broken image
- Expected: Placeholder image displays instead of broken image icon
- No console errors about failed image loads
5. Verify URLs in Database
# Connect to database pod
kubectl exec -it -n my-apps brand-master-db-0 -- psql -U brand_master_user -d brand_master_db
# Check product image URLs
SELECT id, name, images FROM product LIMIT 5;
# Check category image URLs
SELECT id, name, image FROM category;
Expected URL Format:
- New images:
https://api-brand-master.dvirlabs.com/uploads/products/... - Old images (if any):
/uploads/products/...(will still work via ingress routing)
6. Test Direct Image Access
- Upload an image and note the URL returned
- Copy the full URL
- Open in new browser tab
- Expected: Image loads directly
- Test both domains:
https://api-brand-master.dvirlabs.com/uploads/...(backend)https://brand-master.dvirlabs.com/uploads/...(frontend → routed to backend)
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Ingress (Traefik) │
└─────────────────────────────────────────────────────────────┘
│ │
│ api-brand-master.dvirlabs.com │ brand-master.dvirlabs.com
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Backend Svc │ │ Frontend Svc │
│ (Port 8000) │ │ (Port 80) │
└─────────────────┘ └─────────────────┘
│ │
│ ├── /uploads → Backend Svc
│ └── /* → Frontend Pod
▼
┌─────────────────┐
│ Backend Pod │
│ - FastAPI │
│ - Static Files │
│ at /uploads │
└─────────────────┘
│
▼
┌─────────────────┐
│ PVC (15Gi) │
│ /app/uploads │
└─────────────────┘
Image Flow
Upload Flow:
- Admin uploads image via frontend form
- Frontend sends file to
/api/products/upload-imageor/api/categories/upload-image - Backend saves file to
uploads/{folder}/{uuid}{ext}on PVC - Backend returns full URL:
https://api-brand-master.dvirlabs.com/uploads/{folder}/{filename} - Frontend stores full URL in database
Display Flow (New Images):
- Frontend renders
<img src="https://api-brand-master.dvirlabs.com/uploads/..."/> - Browser requests image from backend domain
- Ingress routes to Backend Service
- FastAPI serves file via StaticFiles mount
- Image displays
Display Flow (Old Relative URLs - Backup):
- Frontend renders
<img src="/uploads/..."/> - Browser requests from frontend domain
- Ingress routes
/uploads/*to Backend Service - FastAPI serves file
- Image displays
Troubleshooting
Issue: Images still not loading
Check 1: Verify backend URL is set correctly
kubectl get pod -n my-apps -l app.kubernetes.io/component=backend -o jsonpath='{.items[0].spec.containers[0].env[?(@.name=="BACKEND_URL")].value}'
Should return: https://api-brand-master.dvirlabs.com
Check 2: Verify StaticFiles is mounted
kubectl exec -n my-apps -l app.kubernetes.io/component=backend -- ls -la /app/uploads
Check 3: Verify ingress routes
kubectl get ingress -n my-apps brand-master-frontend -o yaml
Should show /uploads path routing to backend service.
Check 4: Test direct backend access
curl -I https://api-brand-master.dvirlabs.com/uploads/products/some-file.jpg
Issue: Images disappear after pod restart
Check: PVC is properly mounted
kubectl get pvc -n my-apps
kubectl describe pvc -n my-apps brand-master-uploads-pvc
Verify:
- STATUS: Bound
- STORAGECLASS: nfs-client
- CAPACITY: 15Gi
Issue: Upload fails with 500 error
Check backend logs:
kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=100
Common causes:
- Permissions issue on /app/uploads
- PVC not mounted
- Disk space full
Summary of Files Changed
Backend (Code Repo)
- ✅
backend/app/config.py- Added backend_url setting - ✅
backend/app/utils.py- Updated save_upload_file to return full URLs - ✅
backend/app/routers/products.py- Updated to use backend_url - ✅
backend/app/routers/categories.py- Added upload endpoint
Frontend (Code Repo)
- ✅
frontend/src/pages/Admin.jsx- Removed URL building, use backend URLs - ✅
frontend/src/components/ProductCard.jsx- Added fallback handling - ✅
frontend/src/components/CategoryCard.jsx- Added fallback handling
Kubernetes (GitOps Repo)
- ✅
charts/brand-master-chart/templates/frontend-ingress.yaml- Added /uploads routing
Next Steps
- Deploy backend changes
- Deploy frontend changes
- Update Helm deployment with new ingress config
- Test all image upload scenarios
- Verify images persist across restarts
- Monitor logs for any errors
Rollback Plan
If issues occur after deployment:
# Rollback Helm release
helm rollback brand-master -n my-apps
# Or rollback individual components
kubectl rollout undo deployment/brand-master-backend -n my-apps
kubectl rollout undo deployment/brand-master-frontend -n my-apps
Environment Variables Reference
Required in backend deployment:
env:
BACKEND_URL: "https://api-brand-master.dvirlabs.com" # Must be set!
FRONTEND_URL: "https://brand-master.dvirlabs.com"
PYTHONUNBUFFERED: "1"
# ... other env vars ...
Already configured in manifests/brand-master/values.yaml.