{
  "schema_version": 1,
  "generated_at": "2026-10-23T00:00:00.000Z",
  "canonical_url": "https://openlogs.fyi/tools/lychee",
  "slug": "lychee",
  "name": "Lychee",
  "tagline": "Photo management web app for uploading, organizing and sharing albums.",
  "replaces": {
    "summary": "Paid photo hosting services, such as Flickr.",
    "products": [
      {
        "slug": "flickr",
        "name": "Flickr",
        "vendor": "SmugMug",
        "url": "https://openlogs.fyi/alternatives/flickr"
      }
    ]
  },
  "stack": [
    {
      "kind": "language",
      "name": "PHP"
    },
    {
      "kind": "database",
      "name": "MariaDB"
    }
  ],
  "deployment_methods": [
    "docker_compose"
  ],
  "requirements": {
    "min_ram_mb": null,
    "min_cpu_cores": null,
    "source_url": null
  },
  "license": {
    "spdx": "MIT",
    "class": "osi",
    "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/LICENSE"
  },
  "business_model": {
    "value": "unknown",
    "notes": "The README at the pinned commit does not state a business model, so it stays unknown.",
    "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/README.md",
    "checked_at": "2026-10-22"
  },
  "difficulty": {
    "value": "hard",
    "reason": "4 services (1 database or broker: mariadb), 6 volume mounts, no env values to set, host access: cap_add. Heuristic score 6 of 11: hard."
  },
  "repository": {
    "owner": "LycheeOrg",
    "name": "Lychee",
    "url": "https://github.com/LycheeOrg/Lychee"
  },
  "compose_check": {
    "status": "config_ok",
    "validated_at": "2026-10-22",
    "summary": "config_ok on 2026-10-22"
  },
  "recipe": {
    "version": 1,
    "source_path": "docker-compose.minimal.yaml",
    "source_repo": "LycheeOrg/Lychee",
    "source_commit_sha": "d7deef4e25d25df820950fc174b11dfdc498c130",
    "source_kind": "app_repo",
    "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml"
  },
  "install_steps": [
    {
      "order": 1,
      "kind": "prerequisite",
      "language": "text",
      "label": "Docker with the Compose plugin",
      "body": "A host with Docker and the Docker Compose plugin. This entry runs from a compose file.",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/README.md",
      "validation_status": "unvalidated"
    },
    {
      "order": 2,
      "kind": "compose",
      "language": "yaml",
      "label": "Save the compose file as docker-compose.minimal.yaml",
      "body": "# Docker Compose configuration for Lychee application with FrankenPHP backend\n# Version: 2026-01-10\n\nx-base-lychee-setup: &base-lychee-setup\n  image: ghcr.io/lycheeorg/lychee:latest\n  restart: unless-stopped  # Auto-restart at container level (outer layer)\n\n  # Security hardening\n  security_opt:\n    - no-new-privileges:true\n    - seccomp:unconfined  # FrankenPHP may need this; consider custom seccomp profile\n  cap_drop:\n    - ALL\n  cap_add:\n    - CHOWN\n    - SETGID\n    - SETUID\n    - DAC_OVERRIDE\n    - NET_BIND_SERVICE\n  read_only: false  # Laravel needs write access to storage/cache\n  tmpfs:\n    - /tmp:noexec,nosuid,nodev,size=100m\n  volumes:\n    - ./lychee/uploads:/app/public/uploads\n    - ./lychee/logs:/app/storage/logs\n    - ./lychee/tmp:/app/storage/tmp\n  networks:\n    - lychee\n\nx-common-env: &common-env\n  PUID: \"${PUID:-1000}\"\n  PGID: \"${PGID:-1000}\"\n  # REPLACE ME WITH YOUR VALUE GENERATED WITH `openssl rand -base64 32`\n  APP_KEY: \"base64:ucMiFCbfQZUFOxrQz1x9C0OBJeLCCCAieZmnEHXmyGI=\"\n  APP_NAME: \"${APP_NAME:-Lychee}\"\n  APP_ENV: \"${APP_ENV:-production}\"\n  APP_TIMEZONE: \"${TIMEZONE:-UTC}\"\n  APP_URL: \"${APP_URL:-http://localhost:8000}\"\n  APP_FORCE_HTTPS: \"${APP_FORCE_HTTPS:-false}\"\n  DB_CONNECTION: \"${DB_CONNECTION:-mysql}\"\n  DB_HOST: \"${DB_HOST:-lychee_db}\"\n  DB_PORT: \"${DB_PORT:-3306}\"\n  DB_DATABASE: \"${DB_DATABASE:-lychee}\"\n  DB_USERNAME: \"${DB_USERNAME:-lychee}\"\n  DB_PASSWORD: \"${DB_PASSWORD:-password}\"\n  SESSION_DRIVER: \"${SESSION_DRIVER:-file}\"\n  SESSION_LIFETIME: \"${SESSION_LIFETIME:-120}\"\n  QUEUE_CONNECTION: \"${QUEUE_CONNECTION:-database}\"\n  AI_VISION_ENABLED: \"${AI_VISION_ENABLED:-true}\"\n  AI_VISION_FACE_API_KEY: \"${AI_VISION_API_KEY:-changeme}\"\n  AI_VISION_FACE_URL: \"http://lychee_facial_recognition:8000\"\n\nservices:\n  lychee_api:\n    <<: *base-lychee-setup\n    container_name: lychee-api\n    expose:\n      - \"${APP_PORT:-8000}\"\n    ports:\n      - \"${APP_PORT:-8000}:8000\"\n    environment:\n      <<: *common-env\n      # Enable WORKER MODE\n    depends_on:\n      lychee_db:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8000/up\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n      start_period: 30s\n\n  lychee_worker:\n    <<: *base-lychee-setup\n    container_name: lychee-worker\n    environment:\n      <<: *common-env\n      # Enable WORKER MODE\n      LYCHEE_MODE: worker\n\n    depends_on:\n      lychee_db:\n        condition: service_healthy\n      lychee_api:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pgrep -f 'queue:work' || exit 1\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n      start_period: 60s  # Give worker time to start up\n\n  lychee_db:\n      image: mariadb:10\n      security_opt:\n          - no-new-privileges:true\n      cap_drop:\n          - ALL\n      cap_add:\n          - SETGID\n          - SETUID\n          - DAC_OVERRIDE\n          - CHOWN\n      read_only: false  # MariaDB needs write access\n      tmpfs:\n          - /tmp:noexec,nosuid,nodev,size=200m\n          - /var/run/mysqld:noexec,nosuid,nodev,size=10m\n      environment:\n          - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-rootpassword}\n          - MYSQL_DATABASE=${DB_DATABASE:-lychee}\n          - MYSQL_USER=${DB_USERNAME:-lychee}\n          - MYSQL_PASSWORD=${DB_PASSWORD:-password}\n      expose:\n          - 3306\n      volumes:\n          - mysql:/var/lib/mysql\n      networks:\n          - lychee\n      restart: unless-stopped\n      healthcheck:\n        test: [\"CMD\", \"healthcheck.sh\", \"--connect\", \"--innodb_initialized\"]\n        interval: 5s\n        timeout: 3s\n        retries: 10\n        start_period: 10s\n\n  lychee_facial_recognition:\n    container_name: lychee-facial-recognition\n    image: ghcr.io/lycheeorg/lychee-facial-recognition:latest\n    restart: unless-stopped\n    security_opt:\n      - no-new-privileges:true\n    cap_drop:\n      - ALL\n    environment:\n      VISION_FACE_LYCHEE_API_URL: \"http://lychee_api:8000\"\n      VISION_FACE_API_KEY: \"${AI_VISION_API_KEY:-changeme}\"\n      VISION_FACE_VERIFY_SSL: \"${AI_VISION_VERIFY_SSL:-true}\"\n      VISION_FACE_PHOTOS_PATH: \"/data/photos\"\n      VISION_FACE_STORAGE_PATH: \"/data/embeddings\"\n      VISION_FACE_WORKERS: \"${AI_VISION_WORKERS:-1}\"\n    volumes:\n      - ./lychee/uploads:/data/photos:ro\n      - ai_vision_embeddings:/data/embeddings\n    networks:\n      - lychee\n    depends_on:\n      lychee_api:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8000/health\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n      start_period: 60s\n\nnetworks:\n    lychee:\n\nvolumes:\n    mysql:\n        name: lychee_prod_mysql\n        driver: local\n    ai_vision_embeddings:\n        name: lychee_ai_vision_embeddings\n        driver: local\n",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "validation_status": "config_ok"
    },
    {
      "order": 3,
      "kind": "note",
      "language": "text",
      "label": "What the compose file check ran with",
      "body": "The compose file check ran on this file alone. The folder held only this file. The README tells the reader to download the minimal docker-compose template and start it with docker compose -f docker-compose.minimal.yaml up -d.",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/README.md",
      "validation_status": "unvalidated"
    },
    {
      "order": 4,
      "kind": "shell",
      "language": "bash",
      "label": "Start it",
      "body": "docker compose -f docker-compose.minimal.yaml up -d",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/README.md",
      "validation_status": "unvalidated"
    },
    {
      "order": 5,
      "kind": "verify",
      "language": "text",
      "label": "Open the app",
      "body": "Open http://localhost:8000. The compose file publishes host port 8000 for the lychee_api service. The README says to open this address in a browser.",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "validation_status": "unvalidated"
    }
  ],
  "notes": [
    {
      "kind": "gotcha",
      "title": "Some images use the latest tag or no tag",
      "body": "Images without a pinned version: lychee_api: ghcr.io/lycheeorg/lychee:latest, lychee_worker: ghcr.io/lycheeorg/lychee:latest, lychee_facial_recognition: ghcr.io/lycheeorg/lychee-facial-recognition:latest. A later pull can get a different version.",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "checked_at": "2026-10-22"
    },
    {
      "kind": "gotcha",
      "title": "The compose file has placeholder values",
      "body": "Placeholder values: DB_PASSWORD is \"password\" (lychee_api), AI_VISION_FACE_API_KEY is \"changeme\" (lychee_api), DB_PASSWORD is \"password\" (lychee_worker), AI_VISION_FACE_API_KEY is \"changeme\" (lychee_worker), MYSQL_PASSWORD is \"password\" (lychee_db), VISION_FACE_API_KEY is \"changeme\" (lychee_facial_recognition).",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "checked_at": "2026-10-22"
    },
    {
      "kind": "gotcha",
      "title": "The compose file publishes host ports",
      "body": "Host ports: 8000 (lychee_api). A port without a host address listens on all host network interfaces.",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "checked_at": "2026-10-22"
    },
    {
      "kind": "gotcha",
      "title": "The compose file mounts host folders",
      "body": "Host paths mounted into containers: ./lychee/uploads at /app/public/uploads (lychee_api), ./lychee/logs at /app/storage/logs (lychee_api), ./lychee/tmp at /app/storage/tmp (lychee_api), ./lychee/uploads at /app/public/uploads (lychee_worker), ./lychee/logs at /app/storage/logs (lychee_worker), ./lychee/tmp at /app/storage/tmp (lychee_worker), ./lychee/uploads at /data/photos (lychee_facial_recognition).",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "checked_at": "2026-10-22"
    },
    {
      "kind": "gotcha",
      "title": "Some services get elevated host access",
      "body": "lychee_api adds Linux capabilities, lychee_worker adds Linux capabilities, lychee_db adds Linux capabilities.",
      "source_url": "https://github.com/LycheeOrg/Lychee/blob/d7deef4e25d25df820950fc174b11dfdc498c130/docker-compose.minimal.yaml",
      "checked_at": "2026-10-22"
    }
  ],
  "signals": {
    "captured_at": "2026-10-22",
    "stars": 4312,
    "forks": 386,
    "open_issues": 18,
    "last_push": "2026-10-22",
    "days_since_push": 0,
    "latest_release": "v7.11.1",
    "latest_release_date": "2026-10-13",
    "days_since_release": 9
  },
  "links": {
    "html": "https://openlogs.fyi/tools/lychee",
    "markdown": "https://openlogs.fyi/tools/lychee.md",
    "json": "https://openlogs.fyi/tools/lychee.json"
  },
  "updated_at": "2026-10-22"
}
