services: app: build: . image: php-todo-list ports: - "8080:80" environment: APP_DEBUG: "${APP_DEBUG:-false}" # Generated files (SQLite DB + JWT signing key) go here, on their own # volume so they don't overlap the bind mount below. STORAGE_PATH: /var/www/storage # Leave blank to auto-generate a secret into the storage volume on first run. JWT_SECRET: "${JWT_SECRET:-}" JWT_TTL: "${JWT_TTL:-86400}" # Magic links point at the frontend dev server. APP_URL: "${APP_URL:-http://localhost:5173}" # The container has no MTA, so log emails to a file by default. View them with: # docker compose exec app cat /var/www/storage/mail.log # Set to "smtp" (with MAIL_SMTP_*) or "mail" to actually send. MAIL_TRANSPORT: "${MAIL_TRANSPORT:-log}" MAIL_FROM: "${MAIL_FROM:-no-reply@localhost}" MAIL_SMTP_HOST: "${MAIL_SMTP_HOST:-}" MAIL_SMTP_PORT: "${MAIL_SMTP_PORT:-587}" MAIL_SMTP_USERNAME: "${MAIL_SMTP_USERNAME:-}" MAIL_SMTP_PASSWORD: "${MAIL_SMTP_PASSWORD:-}" MAIL_SMTP_ENCRYPTION: "${MAIL_SMTP_ENCRYPTION:-tls}" volumes: # Live source: edit on the host, no image rebuild needed. - .:/var/www/html # Persistent storage, kept outside /var/www/html to avoid a nested mount. - storage:/var/www/storage restart: unless-stopped # Optional Vite dev server. Start it with: docker compose --profile frontend up -d # Without the profile, `docker compose up -d` runs the API alone. web: build: ./web image: php-todo-web profiles: ["frontend"] ports: - "5173:5173" environment: # /api is proxied to the API container on the compose network. VITE_PROXY_TARGET: "http://app:80" volumes: - ./web:/app # Keep the image's platform-specific node_modules; don't let the host's shadow them. - web_node_modules:/app/node_modules depends_on: - app restart: unless-stopped volumes: storage: web_node_modules: