Files
picpeak/TRAEFIK_DEPLOYMENT.md
T
paul ac1cd96ecd
Test and Lint / backend-test (push) Successful in 1m16s
continuous-integration/drone/push Build is failing
Test and Lint / frontend-test (push) Successful in 2m18s
Version and Release / version-bump (push) Successful in 43s
Version and Release / trigger-drone (push) Successful in 4s
fix: resolve frontend API routing issues for Traefik deployment
Major fixes for production deployment with Traefik:

1. API Path Fixes:
   - Remove double /api prefix from all frontend service calls
   - Fix auth.service.ts to use correct paths (/auth/admin/login)
   - Update all services to use single /api prefix from base URL
   - Fix template literal paths in photo services

2. Docker Configuration:
   - Add build args for VITE_API_URL in docker-compose.prod.yml
   - Create Dockerfile.prod with proper API URL configuration
   - Ensure frontend is built with correct API base path

3. Documentation:
   - Add comprehensive TRAEFIK_DEPLOYMENT.md guide
   - Document proper Traefik labels and routing configuration
   - Include troubleshooting steps for common issues
   - Explain network configuration and SSL handling

This resolves:
- 502 Bad Gateway errors
- Double /api/api paths in requests
- Frontend unable to communicate with backend
- Login functionality not working

The frontend now correctly calls the backend API through Traefik's
routing, with all requests going to /api/* being forwarded to the
backend service on port 3000.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-13 23:18:00 +02:00

8.4 KiB

Traefik Deployment Guide

This guide explains how to deploy the PicPeak application with Traefik as the reverse proxy.

Overview

The application consists of:

  • Frontend: React app served by nginx (port 80)
  • Backend: Node.js API (port 3000)
  • Database: PostgreSQL (port 5432, internal only)

Traefik Configuration

1. Docker Labels for Traefik

Add these labels to your docker-compose.prod.yml services:

services:
  frontend:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.picpeak-frontend.rule=Host(`picpeak.yourdomain.com`)"
      - "traefik.http.routers.picpeak-frontend.entrypoints=websecure"
      - "traefik.http.routers.picpeak-frontend.tls.certresolver=letsencrypt"
      - "traefik.http.services.picpeak-frontend.loadbalancer.server.port=80"
      # Priority for catch-all route
      - "traefik.http.routers.picpeak-frontend.priority=1"

  backend:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.picpeak-api.rule=Host(`picpeak.yourdomain.com`) && PathPrefix(`/api`)"
      - "traefik.http.routers.picpeak-api.entrypoints=websecure"
      - "traefik.http.routers.picpeak-api.tls.certresolver=letsencrypt"
      - "traefik.http.services.picpeak-api.loadbalancer.server.port=3000"
      # Higher priority for API routes
      - "traefik.http.routers.picpeak-api.priority=10"
      
      # Additional routes for backend static files
      - "traefik.http.routers.picpeak-uploads.rule=Host(`picpeak.yourdomain.com`) && PathPrefix(`/uploads`)"
      - "traefik.http.routers.picpeak-uploads.entrypoints=websecure"
      - "traefik.http.routers.picpeak-uploads.tls.certresolver=letsencrypt"
      - "traefik.http.routers.picpeak-uploads.service=picpeak-api"
      - "traefik.http.routers.picpeak-uploads.priority=10"
      
      - "traefik.http.routers.picpeak-images.rule=Host(`picpeak.yourdomain.com`) && PathPrefix(`/images`)"
      - "traefik.http.routers.picpeak-images.entrypoints=websecure"
      - "traefik.http.routers.picpeak-images.tls.certresolver=letsencrypt"
      - "traefik.http.routers.picpeak-images.service=picpeak-api"
      - "traefik.http.routers.picpeak-images.priority=10"

2. Network Configuration

Ensure your services are on the Traefik network:

networks:
  picpeak:
    external: false
  traefik:
    external: true

services:
  frontend:
    networks:
      - picpeak
      - traefik
      
  backend:
    networks:
      - picpeak
      - traefik
      
  db:
    networks:
      - picpeak  # Don't expose to traefik

3. Remove Nginx Service

Since you're using Traefik, remove the nginx service from docker-compose.prod.yml:

# Remove this entire service:
# nginx:
#   image: nginx:alpine
#   ...

Frontend Configuration

The frontend is built with the API URL set to /api. This is important because:

  1. All API calls will be relative to the same domain
  2. Traefik will route /api/* to the backend service
  3. No CORS issues since everything is on the same domain

Environment Variables

Ensure these are set correctly:

# Backend needs to know the public URLs
ADMIN_URL=https://picpeak.yourdomain.com
FRONTEND_URL=https://picpeak.yourdomain.com

# Backend API is accessed via /api path
API_URL=https://picpeak.yourdomain.com/api

Complete Example

Here's a complete docker-compose.prod.yml for Traefik:

version: '3.8'

networks:
  picpeak:
    external: false
  traefik:
    external: true

services:
  backend:
    image: picpeak-backend:latest
    build:
      context: ./backend
      dockerfile: Dockerfile
    restart: unless-stopped
    depends_on:
      - db
    environment:
      - NODE_ENV=production
      - PORT=3000
      - JWT_SECRET=${JWT_SECRET}
      - ADMIN_URL=https://picpeak.yourdomain.com
      - FRONTEND_URL=https://picpeak.yourdomain.com
      - DATABASE_CLIENT=pg
      - DB_HOST=db
      - DB_PORT=5432
      - DB_USER=${DB_USER:-picpeak}
      - DB_PASSWORD=${DB_PASSWORD}
      - DB_NAME=${DB_NAME:-picpeak}
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_PORT=${SMTP_PORT}
      - SMTP_SECURE=${SMTP_SECURE}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
      - EMAIL_FROM=${EMAIL_FROM}
      - STORAGE_PATH=/app/storage
      - EVENTS_PATH=/app/storage/events
      - ARCHIVE_PATH=/app/storage/events/archived
    volumes:
      - ./storage:/app/storage
      - ./data:/app/data
      - ./logs:/app/logs
    networks:
      - picpeak
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.picpeak-api.rule=Host(`picpeak.yourdomain.com`) && PathPrefix(`/api`)"
      - "traefik.http.routers.picpeak-api.entrypoints=websecure"
      - "traefik.http.routers.picpeak-api.tls.certresolver=letsencrypt"
      - "traefik.http.services.picpeak-api.loadbalancer.server.port=3000"
      - "traefik.http.routers.picpeak-api.priority=10"
      
      - "traefik.http.routers.picpeak-uploads.rule=Host(`picpeak.yourdomain.com`) && PathPrefix(`/uploads`)"
      - "traefik.http.routers.picpeak-uploads.entrypoints=websecure"
      - "traefik.http.routers.picpeak-uploads.tls.certresolver=letsencrypt"
      - "traefik.http.routers.picpeak-uploads.service=picpeak-api"
      - "traefik.http.routers.picpeak-uploads.priority=10"
      
      - "traefik.http.routers.picpeak-images.rule=Host(`picpeak.yourdomain.com`) && PathPrefix(`/images`)"
      - "traefik.http.routers.picpeak-images.entrypoints=websecure"
      - "traefik.http.routers.picpeak-images.tls.certresolver=letsencrypt"
      - "traefik.http.routers.picpeak-images.service=picpeak-api"
      - "traefik.http.routers.picpeak-images.priority=10"

  frontend:
    image: picpeak-frontend:latest
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        - VITE_API_URL=/api
    restart: unless-stopped
    depends_on:
      - backend
    networks:
      - picpeak
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.picpeak-frontend.rule=Host(`picpeak.yourdomain.com`)"
      - "traefik.http.routers.picpeak-frontend.entrypoints=websecure"
      - "traefik.http.routers.picpeak-frontend.tls.certresolver=letsencrypt"
      - "traefik.http.services.picpeak-frontend.loadbalancer.server.port=80"
      - "traefik.http.routers.picpeak-frontend.priority=1"

  db:
    image: postgres:14-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${DB_USER:-picpeak}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME:-picpeak}
      - POSTGRES_HOST_AUTH_METHOD=scram-sha-256
      - POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256 --auth-local=trust
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - picpeak
    command: postgres -c ssl=off

volumes:
  postgres_data:

Troubleshooting

502 Bad Gateway Errors

  1. Check if backend is running:

    docker-compose -f docker-compose.prod.yml ps
    docker-compose -f docker-compose.prod.yml logs backend
    
  2. Verify Traefik can reach the backend:

    • Ensure both services are on the same Docker network
    • Check Traefik logs: docker logs traefik
  3. Check backend health:

    docker-compose -f docker-compose.prod.yml exec backend curl http://localhost:3000/api/health
    

Frontend Can't Reach API

  1. Verify API paths don't have double /api:

    • Frontend should call /auth/admin/login, not /api/auth/admin/login
    • The base URL in axios should be /api
  2. Check browser console for actual URLs being called

  3. Ensure Traefik routing rules are correct:

    • API routes should have higher priority than frontend catch-all

CORS Issues

Should not occur since everything is on the same domain. If you see CORS errors:

  1. Check that FRONTEND_URL and ADMIN_URL match your actual domain
  2. Ensure you're not mixing HTTP and HTTPS

Testing the Setup

  1. Test API directly:

    curl https://picpeak.yourdomain.com/api/health
    
  2. Test frontend:

    curl https://picpeak.yourdomain.com/
    
  3. Test admin login:

Important Notes

  1. SSL/TLS: Traefik handles SSL termination, so the backend doesn't need SSL
  2. Port Exposure: Don't expose backend ports directly - let Traefik handle routing
  3. Health Checks: Configure Traefik health checks for better reliability
  4. Rate Limiting: Consider adding Traefik rate limiting middleware for API routes