services: # --- Base de datos MariaDB --- db: image: mariadb:lts-noble container_name: factuges_acana_db restart: unless-stopped environment: MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASS} MARIADB_USER: ${DB_USER} MARIADB_PASSWORD: ${DB_PASS} MARIADB_DATABASE: ${DB_NAME} volumes: - ./volumes/db_data:/var/lib/mysql networks: - internal healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 20s timeout: 5s retries: 10 # --- phpMyAdmin (solo accesible por Traefik + whitelist IP) --- phpmyadmin: image: phpmyadmin/phpmyadmin:latest container_name: factuges_acana_phpmyadmin restart: unless-stopped environment: PMA_ARBITRARY: 1 PMA_ABSOLUTE_URI: https://acana.factuges.app/phpmyadmin/ PMA_HOST: db PMA_USER: ${DB_USER} PMA_PASSWORD: ${DB_PASS} PMA_VERBOSES: "FactuGES Acana" MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS} UPLOAD_LIMIT: 64M depends_on: - db networks: - internal - edge labels: traefik.enable: "true" # /phpmyadmin → phpMyAdmin traefik.http.routers.factuges_acana_phpmyadmin.rule: "Host(`${DOMAIN}`) && PathPrefix(`/phpmyadmin/`)" traefik.http.routers.factuges_acana_phpmyadmin.entrypoints: websecure #traefik.http.routers.factuges_acana_phpmyadmin.tls: true traefik.http.routers.factuges_acana_phpmyadmin.tls.certresolver: cloudflare-dns traefik.http.routers.factuges_acana_phpmyadmin.priority: "110" traefik.http.routers.factuges_acana_phpmyadmin.service: factuges_acana_phpmyadmin traefik.http.services.factuges_acana_phpmyadmin.loadbalancer.server.port: "80" # strip /phpmyadmin del path antes de llegar al contenedor traefik.http.middlewares.factuges_acana_phpmyadmin_strip.stripprefix.prefixes: "/phpmyadmin/" # whitelist a tu IP traefik.http.middlewares.factuges_acana_phpmyadmin_ipwhitelist.ipwhitelist.sourcerange: "79.116.183.41/32" # encadenar middlewares traefik.http.routers.factuges_acana_phpmyadmin.middlewares: "factuges_acana_phpmyadmin_strip,factuges_acana_phpmyadmin_ipwhitelist" # --- API Node.js --- api: image: ${API_IMAGE} container_name: factuges_acana_api restart: unless-stopped depends_on: db: condition: service_healthy environment: NODE_ENV: production COMPANY: acana PORT: ${API_PORT:-3002} DB_DIALECT: "mysql" DB_HOST: "db" DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} DB_USER: ${DB_USER} DB_PASS: ${DB_PASS} FRONTEND_URL: ${FRONTEND_URL} networks: - internal - edge labels: traefik.enable: "true" # /api → backend traefik.http.routers.factuges_acana_api.rule: Host(`${DOMAIN}`) && PathPrefix(`/api`) traefik.http.routers.factuges_acana_api.entrypoints: websecure traefik.http.routers.factuges_acana_api.tls.certresolver: cloudflare-dns traefik.http.routers.factuges_acana_api.priority: "100" traefik.http.routers.factuges_acana_api.service: factuges_acana_api # Servicio traefik.http.services.factuges_acana_api.loadbalancer.server.port: "${API_PORT:-3002}" # --- Web estática Vite (nginx) --- web: image: nginx:alpine container_name: factuges_acana_web restart: unless-stopped depends_on: - api environment: VITE_API_SERVER_URL: ${DOMAIN}/api volumes: - ./web/public:/usr/share/nginx/html:ro - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro networks: - edge labels: traefik.enable: "true" # / → frontend traefik.http.routers.factuges_acana_web.rule: Host(`${DOMAIN}`) && PathPrefix(`/`) traefik.http.routers.factuges_acana_web.entrypoints: websecure traefik.http.routers.factuges_acana_web.tls.certresolver: cloudflare-dns # prioridad más baja para que /api y /phpmyadmin ganen traefik.http.routers.factuges_acana_web.priority: "1" traefik.http.routers.factuges_acana_web.service: factuges_acana_web traefik.http.services.factuges_acana_web.loadbalancer.server.port: "80" factuges_sync: image: "factuges-sync:acana-latest" container_name: "factuges-sync-acana" restart: "no" environment: ENV: "production" LOCAL_TZ: "Europe/Madrid" STATE_PATH: "${STATE_PATH}" FACTUGES_HOST: "${FACTUGES_HOST}" FACTUGES_PORT: "${FACTUGES_PORT}" FACTUGES_DATABASE: "${FACTUGES_DATABASE}" FACTUGES_USER: "${FACTUGES_USER}" FACTUGES_PASSWORD: "${FACTUGES_PASSWORD}" FWEB_MYSQL_HOST: "db" FWEB_MYSQL_PORT: "${DB_PORT}" FWEB_MYSQL_DATABASE: "${DB_NAME}" FWEB_MYSQL_USER: "${DB_USER}" FWEB_MYSQL_PASSWORD: "${DB_PASS}" CTE_COMPANY_ID: "${CTE_COMPANY_ID}" CTE_SERIE: "${CTE_SERIE}" CTE_STATUS_INVOICE: "${CTE_STATUS_INVOICE}" CTE_IS_PROFORMA: "${CTE_IS_PROFORMA}" CTE_STATUS_VERIFACTU: "${CTE_STATUS_VERIFACTU}" CTE_LANGUAGE_CODE: "${CTE_LANGUAGE_CODE}" CTE_COUNTRY_CODE: "${CTE_COUNTRY_CODE}" CTE_IS_COMPANY: "${CTE_IS_COMPANY}" CTE_SYNC_RESULT_OK: "${CTE_SYNC_RESULT_OK}" CTE_SYNC_RESULT_FAIL: "${CTE_SYNC_RESULT_FAIL}" VERIFACTU_API_KEY: "${VERIFACTU_API_KEY}" VERIFACTU_BASE_URL: "${VERIFACTU_BASE_URL}" VERIFACTU_NIFS_API_KEY: "${VERIFACTU_NIFS_API_KEY}" depends_on: db: condition: service_healthy networks: - internal - edge volumes: - ./volumes/db_sync:/app/state networks: edge: name: edge external: true internal: name: factuges_acana_internal external: false volumes: # Solo declarativo; usamos ruta ./volumes/db_data arriba db_data: