version: '3.8' services: traefik: image: traefik:v3.0 container_name: traefik restart: unless-stopped command: # API und Dashboard - --api.dashboard=true - --api.insecure=false # Entrypoints - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 # Docker Provider - --providers.docker=true - --providers.docker.exposedbydefault=false # Let's Encrypt - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL} - --certificatesresolvers.letsencrypt.acme.storage=/acme.json - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web # Logging - --log.level=INFO - --accesslog=true # Global HTTP -> HTTPS redirect - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.web.http.redirections.entrypoint.permanent=true ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_acme:/acme.json labels: # Dashboard - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)" - "traefik.http.routers.traefik.entrypoints=websecure" - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.middlewares=auth" # Basic Auth für Dashboard (admin:admin - bitte ändern!) - "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$10$$8eO9J8Ef.LswB5K4l1.ZJ.qZBOa6ZXJ3X2y3zCZLCr9zHVJ8vJ2Ga" networks: - traefik-network # Connect to external MySQL network - ${EXTERNAL_MYSQL_NETWORK:-gitea_default} backend: # Use pre-built image from registry instead of building image: ${BACKEND_IMAGE:-ghcr.io/your-username/rezepte-klaus-backend:latest} container_name: rezepte-backend-prod restart: unless-stopped environment: - NODE_ENV=production - DATABASE_URL=mysql://rezepte_user:${MYSQL_REZEPTE_PASSWORD}@${MYSQL_HOST:-mysql}:${MYSQL_PORT:-3306}/rezepte_klaus - CORS_ORIGIN=https://rezepte.${DOMAIN} - PORT=3001 volumes: - uploads_data:/app/uploads # Legacy uploads can be mounted if needed # - ./legacy-uploads:/app/legacy-uploads:ro healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"] interval: 30s timeout: 10s retries: 3 labels: - "traefik.enable=true" # API Routes - "traefik.http.routers.backend.rule=Host(`rezepte.${DOMAIN}`) && PathPrefix(`/api`)" - "traefik.http.routers.backend.entrypoints=websecure" - "traefik.http.routers.backend.tls.certresolver=letsencrypt" - "traefik.http.services.backend.loadbalancer.server.port=3001" # Upload Routes - "traefik.http.routers.backend-uploads.rule=Host(`rezepte.${DOMAIN}`) && PathPrefix(`/uploads`)" - "traefik.http.routers.backend-uploads.entrypoints=websecure" - "traefik.http.routers.backend-uploads.tls.certresolver=letsencrypt" - "traefik.http.routers.backend-uploads.service=backend" networks: - traefik-network # Connect to external MySQL network - ${EXTERNAL_MYSQL_NETWORK:-gitea_default} frontend: # Use pre-built image from registry instead of building image: ${FRONTEND_IMAGE:-ghcr.io/your-username/rezepte-klaus-frontend:latest} container_name: rezepte-frontend-prod restart: unless-stopped depends_on: - backend healthcheck: test: ["CMD", "curl", "-f", "http://localhost/"] interval: 30s timeout: 10s retries: 3 labels: - "traefik.enable=true" # Frontend Routes (catch-all) - "traefik.http.routers.frontend.rule=Host(`rezepte.${DOMAIN}`)" - "traefik.http.routers.frontend.entrypoints=websecure" - "traefik.http.routers.frontend.tls.certresolver=letsencrypt" - "traefik.http.services.frontend.loadbalancer.server.port=80" # Lower priority than backend routes - "traefik.http.routers.frontend.priority=1" - "traefik.http.routers.backend.priority=10" - "traefik.http.routers.backend-uploads.priority=10" networks: - traefik-network phpmyadmin: image: phpmyadmin/phpmyadmin:latest container_name: rezepte-phpmyadmin restart: unless-stopped environment: - PMA_HOST=${MYSQL_HOST:-mysql} - PMA_PORT=${MYSQL_PORT:-3306} - PMA_USER=${MYSQL_ADMIN_USER:-root} - PMA_PASSWORD=${MYSQL_ADMIN_PASSWORD} - UPLOAD_LIMIT=2G - MEMORY_LIMIT=2G - MAX_EXECUTION_TIME=0 healthcheck: test: ["CMD", "curl", "-f", "http://localhost/"] interval: 30s timeout: 10s retries: 3 labels: - "traefik.enable=true" - "traefik.http.routers.phpmyadmin.rule=Host(`phpmyadmin.${DOMAIN}`)" - "traefik.http.routers.phpmyadmin.entrypoints=websecure" - "traefik.http.routers.phpmyadmin.tls.certresolver=letsencrypt" - "traefik.http.services.phpmyadmin.loadbalancer.server.port=80" # Optional: Add basic auth for extra security # - "traefik.http.routers.phpmyadmin.middlewares=auth" networks: - traefik-network # Connect to external MySQL network - ${EXTERNAL_MYSQL_NETWORK:-gitea_default} portainer: image: portainer/portainer-ce:latest container_name: portainer restart: unless-stopped command: -H unix:///var/run/docker.sock volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data labels: - "traefik.enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN}`)" - "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" - "traefik.http.services.portainer.loadbalancer.server.port=9000" # Optional: Add basic auth for extra security # - "traefik.http.routers.portainer.middlewares=auth" networks: - traefik-network volumes: uploads_data: driver: local traefik_acme: driver: local portainer_data: driver: local networks: traefik-network: driver: bridge # Reference to external network (will be created by Gitea) # This network should already exist from your Gitea installation gitea_default: external: true