Lychee
Photo management web app for uploading, organizing and sharing albums.
- Replaces
-
Paid photo hosting services, such as Flickr.
All open-source alternatives to Flickr
- Difficulty
-
Difficulty: Hard
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.
- Stack
PHP (language), MariaDB (database)
- Deployment
docker_compose
- Requirements
Not stated.
- License
-
MIT(OSI-approved open source) - Business model
-
Unknown
The README at the pinned commit does not state a business model, so it stays unknown.
Source: github.com/LycheeOrg/Lychee/blob/d7deef4/README.md, read on .
- Repository
GitHub stats
- Stars
- 4,312
- Forks
- 386
- Open issues and pull requests
- 18
- Last push
- 2026-10-22
- Latest release
- v7.11.1
- Released
- 2026-10-13
From the GitHub API on . These numbers are a snapshot, not live. GitHub detected the license as MIT.
Compose file
# Docker Compose configuration for Lychee application with FrankenPHP backend
# Version: 2026-01-10
x-base-lychee-setup: &base-lychee-setup
image: ghcr.io/lycheeorg/lychee:latest
restart: unless-stopped # Auto-restart at container level (outer layer)
# Security hardening
security_opt:
- no-new-privileges:true
- seccomp:unconfined # FrankenPHP may need this; consider custom seccomp profile
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
- DAC_OVERRIDE
- NET_BIND_SERVICE
read_only: false # Laravel needs write access to storage/cache
tmpfs:
- /tmp:noexec,nosuid,nodev,size=100m
volumes:
- ./lychee/uploads:/app/public/uploads
- ./lychee/logs:/app/storage/logs
- ./lychee/tmp:/app/storage/tmp
networks:
- lychee
x-common-env: &common-env
PUID: "${PUID:-1000}"
PGID: "${PGID:-1000}"
# REPLACE ME WITH YOUR VALUE GENERATED WITH `openssl rand -base64 32`
APP_KEY: "base64:ucMiFCbfQZUFOxrQz1x9C0OBJeLCCCAieZmnEHXmyGI="
APP_NAME: "${APP_NAME:-Lychee}"
APP_ENV: "${APP_ENV:-production}"
APP_TIMEZONE: "${TIMEZONE:-UTC}"
APP_URL: "${APP_URL:-http://localhost:8000}"
APP_FORCE_HTTPS: "${APP_FORCE_HTTPS:-false}"
DB_CONNECTION: "${DB_CONNECTION:-mysql}"
DB_HOST: "${DB_HOST:-lychee_db}"
DB_PORT: "${DB_PORT:-3306}"
DB_DATABASE: "${DB_DATABASE:-lychee}"
DB_USERNAME: "${DB_USERNAME:-lychee}"
DB_PASSWORD: "${DB_PASSWORD:-password}"
SESSION_DRIVER: "${SESSION_DRIVER:-file}"
SESSION_LIFETIME: "${SESSION_LIFETIME:-120}"
QUEUE_CONNECTION: "${QUEUE_CONNECTION:-database}"
AI_VISION_ENABLED: "${AI_VISION_ENABLED:-true}"
AI_VISION_FACE_API_KEY: "${AI_VISION_API_KEY:-changeme}"
AI_VISION_FACE_URL: "http://lychee_facial_recognition:8000"
services:
lychee_api:
<<: *base-lychee-setup
container_name: lychee-api
expose:
- "${APP_PORT:-8000}"
ports:
- "${APP_PORT:-8000}:8000"
environment:
<<: *common-env
# Enable WORKER MODE
depends_on:
lychee_db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/up"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
lychee_worker:
<<: *base-lychee-setup
container_name: lychee-worker
environment:
<<: *common-env
# Enable WORKER MODE
LYCHEE_MODE: worker
depends_on:
lychee_db:
condition: service_healthy
lychee_api:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'queue:work' || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s # Give worker time to start up
lychee_db:
image: mariadb:10
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
- DAC_OVERRIDE
- CHOWN
read_only: false # MariaDB needs write access
tmpfs:
- /tmp:noexec,nosuid,nodev,size=200m
- /var/run/mysqld:noexec,nosuid,nodev,size=10m
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-rootpassword}
- MYSQL_DATABASE=${DB_DATABASE:-lychee}
- MYSQL_USER=${DB_USERNAME:-lychee}
- MYSQL_PASSWORD=${DB_PASSWORD:-password}
expose:
- 3306
volumes:
- mysql:/var/lib/mysql
networks:
- lychee
restart: unless-stopped
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
lychee_facial_recognition:
container_name: lychee-facial-recognition
image: ghcr.io/lycheeorg/lychee-facial-recognition:latest
restart: unless-stopped
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
environment:
VISION_FACE_LYCHEE_API_URL: "http://lychee_api:8000"
VISION_FACE_API_KEY: "${AI_VISION_API_KEY:-changeme}"
VISION_FACE_VERIFY_SSL: "${AI_VISION_VERIFY_SSL:-true}"
VISION_FACE_PHOTOS_PATH: "/data/photos"
VISION_FACE_STORAGE_PATH: "/data/embeddings"
VISION_FACE_WORKERS: "${AI_VISION_WORKERS:-1}"
volumes:
- ./lychee/uploads:/data/photos:ro
- ai_vision_embeddings:/data/embeddings
networks:
- lychee
depends_on:
lychee_api:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
lychee:
volumes:
mysql:
name: lychee_prod_mysql
driver: local
ai_vision_embeddings:
name: lychee_ai_vision_embeddings
driver: local
From the app's own repository: github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml.
Compose file from the app repository.
Install
- Docker with the Compose plugin (github.com/LycheeOrg/Lychee/blob/d7deef4/README.md)
A host with Docker and the Docker Compose plugin. This entry runs from a compose file.
- Save the compose file as docker-compose.minimal.yaml (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml)
Use the compose file shown above.
- What the compose file check ran with (github.com/LycheeOrg/Lychee/blob/d7deef4/README.md)
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.
- Start it (github.com/LycheeOrg/Lychee/blob/d7deef4/README.md)
docker compose -f docker-compose.minimal.yaml up -d - Open the app (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml)
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.
Compose file check: config_ok on 2026-10-22.
Gotchas and maintenance
Gotchas
- Some images use the latest tag or no tag. 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. (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml, checked 2026-10-22)
- The compose file has placeholder values. 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). (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml, checked 2026-10-22)
- The compose file publishes host ports. Host ports: 8000 (lychee_api). A port without a host address listens on all host network interfaces. (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml, checked 2026-10-22)
- The compose file mounts host folders. 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). (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml, checked 2026-10-22)
- Some services get elevated host access. lychee_api adds Linux capabilities, lychee_worker adds Linux capabilities, lychee_db adds Linux capabilities. (github.com/LycheeOrg/Lychee/blob/d7deef4/docker-compose.minimal.yaml, checked 2026-10-22)
Signals
- Open issues and pull requests: 18
- Last push: 2026-10-22 (0 days before this page was built)
- Latest release: v7.11.1 on 2026-10-13 (9 days before this page was built)