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}" # Deliver to the Mailpit catcher below; read mail at http://localhost:8025. MAIL_TRANSPORT: "${MAIL_TRANSPORT:-smtp}" MAIL_FROM: "${MAIL_FROM:-no-reply@todo.test}" MAIL_SMTP_HOST: "${MAIL_SMTP_HOST:-mailpit}" MAIL_SMTP_PORT: "${MAIL_SMTP_PORT:-1025}" MAIL_SMTP_USERNAME: "${MAIL_SMTP_USERNAME:-}" MAIL_SMTP_PASSWORD: "${MAIL_SMTP_PASSWORD:-}" MAIL_SMTP_ENCRYPTION: "${MAIL_SMTP_ENCRYPTION:-none}" 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 depends_on: - mailpit restart: unless-stopped # Development mail catcher (Mailpit — the maintained MailHog successor). ~15 MB, # single Go binary, messages held in memory. Web UI: http://localhost:8025 mailpit: image: axllent/mailpit:v1.31 ports: - "8025:8025" # web UI + REST API - "1025:1025" # SMTP (also reachable in-network as mailpit:1025) 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: