A great looking and easy-to-use photo-management-system you can run on your server, to manage and share photos.
docker pull ghcr.io/lycheeorg/lychee
docker run -d --name lychee -p 8000:8000 --restart always ghcr.io/lycheeorg/lychee:latest
podman run -d --name lychee -p 8000:8000 --restart always ghcr.io/lycheeorg/lychee:latest
skopeo inspect docker://ghcr.io/lycheeorg/lychee:latest
oras pull ghcr.io/lycheeorg/lychee:latest
docker compose up -d
# 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