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 - rezepte-network mysql: image: mysql:8.0 container_name: rezepte-mysql-prod restart: unless-stopped environment: MYSQL_DATABASE: rezepte_klaus MYSQL_USER: rezepte_user MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change_this_password} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-change_this_root_password} volumes: - mysql_data:/var/lib/mysql # SQL files must be present on server - ./Rezepte.sql:/docker-entrypoint-initdb.d/01-rezepte.sql:ro - ./ingredients.sql:/docker-entrypoint-initdb.d/02-ingredients.sql:ro - ./Zubereitung.sql:/docker-entrypoint-initdb.d/03-zubereitung.sql:ro - ./rezepte_bilder.sql:/docker-entrypoint-initdb.d/04-bilder.sql:ro healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 networks: - rezepte-network 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_PASSWORD:-change_this_password}@mysql: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 depends_on: mysql: condition: service_healthy 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 - rezepte-network 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 - PMA_PORT=3306 - PMA_USER=root - PMA_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - UPLOAD_LIMIT=2G - MEMORY_LIMIT=2G - MAX_EXECUTION_TIME=0 depends_on: mysql: condition: service_healthy 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 - rezepte-network 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: mysql_data: driver: local uploads_data: driver: local traefik_acme: driver: local portainer_data: driver: local networks: traefik-network: driver: bridge rezepte-network: driver: bridge