# Coolify deployment — Build Pack: Docker Compose # This is the production deploy stack (MariaDB + Redis). The ddev local env uses # its own compose.yaml; do not confuse the two. # Select this file (docker-compose.yml) as the Compose file in the Coolify resource. # Do NOT define custom `networks:` — Coolify manages the network; custom ones # break Traefik routing (per Coolify docs). # Shared application environment, reused by app + both workers. x-app-env: &app-env APP_ENV: prod APP_DEBUG: "0" APP_SECRET: ${APP_SECRET} DATABASE_URL: "mysql://clinic:${DB_PASSWORD}@mariadb:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4" MESSENGER_TRANSPORT_DSN: redis://redis:6379/messages REDIS_URL: redis://redis:6379 JWT_SECRET_KEY: "%kernel.project_dir%/config/jwt/private.pem" JWT_PUBLIC_KEY: "%kernel.project_dir%/config/jwt/public.pem" JWT_PASSPHRASE: ${JWT_PASSPHRASE} CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN} DEFAULT_URI: ${APP_BASE_URL} APP_BASE_URL: ${APP_BASE_URL} ALLOWED_FRONTEND_HOSTS: ${ALLOWED_FRONTEND_HOSTS} TRUSTED_PROXIES: ${TRUSTED_PROXIES} API_IR_BASE_URL: ${API_IR_BASE_URL} API_IR_TOKEN: ${API_IR_TOKEN} REFRESH_TOKEN_TTL: "2592000" OTP_TTL: "1200" MAX_FILE_SIZE_BYTES: "5242880" UPLOAD_DIR: var/uploads # Shared volume mounts for services that read/write JWT keys and uploads. x-app-volumes: &app-volumes - jwt_keys:/app/config/jwt - uploads_public:/app/public/uploads - uploads_var:/app/var/uploads # Shared dependency gate on DB + Redis being up. x-app-depends: &app-depends mariadb: condition: service_healthy redis: condition: service_started services: # Web app (PHP-FPM + Nginx). # In Coolify, assign ALL serving domains to THIS service (port 80) — the backend # API host plus every frontend domain you want Traefik to route + issue TLS for. # Coolify supports a comma-separated domain list on the service. # CORS/payload allow-listing for those frontends is handled separately via # CORS_ALLOW_ORIGIN / ALLOWED_FRONTEND_HOSTS (see docker/frontend-domains.json). app: build: context: . dockerfile: Dockerfile restart: unless-stopped environment: <<: *app-env RUN_INIT: "1" # runs JWT keygen + migrations on start (only this service) volumes: *app-volumes depends_on: *app-depends healthcheck: test: ["CMD", "php", "-r", "exit(@fsockopen('127.0.0.1', 80) ? 0 : 1);"] interval: 15s timeout: 5s retries: 5 start_period: 60s # SMS / async message consumer. worker-async: build: context: . dockerfile: Dockerfile restart: unless-stopped command: php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v environment: <<: *app-env RUN_INIT: "0" volumes: *app-volumes depends_on: *app-depends # Scheduler consumer — expires unpaid appointments every minute. worker-scheduler: build: context: . dockerfile: Dockerfile restart: unless-stopped command: php bin/console messenger:consume scheduler_default --time-limit=3600 -v environment: <<: *app-env RUN_INIT: "0" volumes: *app-volumes depends_on: *app-depends mariadb: image: mariadb:11.8 restart: unless-stopped environment: MARIADB_DATABASE: clinic_pro MARIADB_USER: clinic MARIADB_PASSWORD: ${DB_PASSWORD} MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} volumes: - mariadb_data:/var/lib/mysql healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 10 start_period: 30s redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data volumes: jwt_keys: uploads_public: uploads_var: mariadb_data: redis_data: