The recommended way to install Lychee is via Docker using the official image at ghcr.io/lycheeorg/lychee. The Docker image provides a complete, production-ready deployment with all dependencies included.
As of Version 7, the default Docker image uses FrankenPHP with Laravel Octane instead of the traditional nginx + PHP-FPM stack. This modern architecture delivers dramatic performance improvements:
- Boot time reduced from 40-60ms to 4-6ms per request
- 3-4x better throughput
- Significantly reduced latency
- Framework components kept in memory and reused across requests
The following tags are available:
latest: Latest stable Lychee release using FrankenPHPv[NUMBER]: Specific stable version (e.g.,v7.0.0) using FrankenPHPedge: Current development/bleeding edge builds using FrankenPHPlegacy: Latest release using nginx + PHP-FPM (deprecated)v[NUMBER]-legacy: Specific stable version using nginx + PHP-FPM (deprecated)
Recommendation: Use latest or specific version tags for production deployments.
The recommended way to deploy Lychee is with Docker Compose. Use the official template as a starting point:
https://github.com/LycheeOrg/Lychee/blob/master/docker-compose.yaml
Start the services:
docker-compose up -dThe current volume structure for Version 7 and later:
volumes:
- ./lychee/uploads:/app/public/uploads # Photo storage
- ./lychee/storage/app:/app/storage/app # Application storage
- ./lychee/logs:/app/storage/logs # Log files
- ./lychee/tmp:/app/storage/tmp # Temporary files
- ./lychee/conf/.env:/app/.env:ro # Environment config (read-only)
- ./conf/user.css:/app/public/dist/user.css # Optional: Custom CSS
- ./conf/custom.js:/app/public/dist/custom.js # Optional: Custom JavaScriptIf using the legacy image:
volumes:
- ./lychee/conf:/conf # Configuration
- ./lychee/uploads:/uploads # Photo storage
- ./lychee/sym:/sym # Symbolic links
- ./lychee/logs:/logs # Log files
- ./lychee/tmp:/lychee-tmp # Temporary filesImportant: If you're upgrading from Version 6 to Version 7, you must update your volume mounts. See the upgrade documentation for migration steps.
Configure your database connection:
environment:
- DB_CONNECTION=mysql # mysql, pgsql, or sqlite
- DB_HOST=lychee_db # Database hostname
- DB_PORT=3306 # Database port
- DB_DATABASE=lychee # Database name
- DB_USERNAME=lychee # Database username
- DB_PASSWORD=lychee # Database passwordCommon environment variables:
environment:
- APP_URL=http://localhost:8000 # Your public URL
- APP_DEBUG=false # Enable debug mode (development only)
- APP_KEY=base64:YOUR_APP_KEY_HERE # Application encryption key (generate with `php artisan key:generate --show` or use `openssl rand -base64 32`)
- TIMEZONE=UTC # Server timezone
- LOG_CHANNEL=stack # Logging channelenvironment:
- PUID=1000 # User ID for file permissions
- PGID=1000 # Group ID for file permissions
- PHP_TZ=UTC # PHP timezone
#- RUN_AS_ROOT=yes # Run PHP processes as root (yes/no), disabled by default for securityVersion 7 introduces worker mode for processing background jobs independently. This enables horizontal scaling for improved performance with large photo uploads and processing tasks.
The basic single-service setup handles both web requests and background jobs. However, the requests are limited to 30s by default, which may not be sufficient for large uploads or processing.
services:
lychee:
image: ghcr.io/lycheeorg/lychee:latest
# ... volumes, environment, etc.For better performance, run dedicated worker services:
services:
lychee_api:
image: ghcr.io/lycheeorg/lychee:latest
container_name: lychee
ports:
- "8000:8000"
volumes:
- ./lychee/uploads:/app/public/uploads
- ./lychee/storage/app:/app/storage/app
- ./lychee/logs:/app/storage/logs
- ./lychee/tmp:/app/storage/tmp
- ./lychee/conf/.env:/app/.env:ro
environment:
- DB_CONNECTION=mysql
- DB_HOST=lychee_db
- QUEUE_CONNECTION=database # CRITICAL: Enable queue processing
# ... other environment variables
depends_on:
- lychee_db
networks:
- lychee
lychee_worker:
image: ghcr.io/lycheeorg/lychee:latest
container_name: lychee_worker
volumes:
- ./lychee/uploads:/app/public/uploads
- ./lychee/storage/app:/app/storage/app
- ./lychee/logs:/app/storage/logs
- ./lychee/tmp:/app/storage/tmp
- ./lychee/conf/.env:/app/.env:ro
environment:
- LYCHEE_MODE=worker # CRITICAL: Run in worker mode
- DB_CONNECTION=mysql
- DB_HOST=lychee_db
- DB_PORT=3306
- DB_DATABASE=lychee
- DB_USERNAME=lychee
- DB_PASSWORD=lychee
- QUEUE_CONNECTION=database # CRITICAL: Must match API service
# ... other environment variables
depends_on:
- lychee_db
- lychee_api
networks:
- lycheeCritical Requirements for Worker Mode:
- Set
QUEUE_CONNECTION=database(orredis) in both API and worker services - Set
LYCHEE_MODE=workerin worker service only - Ensure both services share the same database and volume mounts
Run multiple worker instances for parallel processing:
Option 1: Using replicas (Docker Swarm/Compose v3)
lychee_worker:
image: ghcr.io/lycheeorg/lychee:latest
deploy:
replicas: 3 # Run 3 worker instances
# ... rest of configurationOption 2: Multiple named services
lychee_worker_1:
image: ghcr.io/lycheeorg/lychee:latest
container_name: lychee_worker_1
environment:
- LYCHEE_MODE=worker
# ... rest of configuration
lychee_worker_2:
image: ghcr.io/lycheeorg/lychee:latest
container_name: lychee_worker_2
environment:
- LYCHEE_MODE=worker
# ... rest of configurationFor sensitive information, use Docker secrets instead of environment variables:
services:
lychee:
image: ghcr.io/lycheeorg/lychee:latest
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
- APP_KEY_FILE=/run/secrets/app_key
# - REDIS_PASSWORD_FILE=/run/secrets/redis_password
# - MAIL_PASSWORD_FILE=/run/secrets/mail_password
- ADMIN_PASSWORD_FILE=/run/secrets/admin_password
secrets:
- db_password
- app_key
# - redis_password
# - mail_password
- admin_password
secrets:
app_key:
file: ./secrets/app_key.txt
db_password:
file: ./secrets/db_password.txt
# redis_password:
# file: ./secrets/redis_password.txt
# mail_password:
# file: ./secrets/mail_password.txt
admin_password:
file: ./secrets/admin_password.txtSupported _FILE variables:
APP_KEY_FILEDB_PASSWORD_FILE
ADMIN_PASSWORD_FILE
Important: Due to the FrankenPHP architecture, the .env file is read once at container startup and kept in memory. Any changes to the .env file require a container restart to take effect:
docker-compose restart lycheeConfiguration values are applied in this order (highest to lowest priority):
- Environment variables passed via
docker-compose.ymlordocker run - Values in mounted
.envfile - Default values from
.env.example
Note: When restarting containers, environment variables from docker-compose will override values in the mounted .env file. Modify configuration in your docker-compose file rather than editing files inside the container.
Version 7 introduces breaking changes to the Docker setup. You must update your docker-compose configuration when upgrading from v6 to v7. Simply changing the image tag will not work.
See the detailed upgrade documentation for complete migration instructions, including:
- Volume mount changes
- Service architecture updates
- Environment variable changes
- Worker mode configuration
For routine updates:
-
Backup your data:
# Backup database docker exec lychee_db mysqldump -u lychee -p lychee > lychee_backup.sql # Backup uploads cp -r ./lychee ./lychee_backup
-
Pull the latest image:
docker-compose pull
-
Restart services:
docker-compose down docker-compose up -d
-
Run migrations:
docker exec lychee php artisan migrate
docker-compose ps# All services
docker-compose logs -f
# Specific service
docker-compose logs -f lychee
# Worker logs
docker-compose logs -f lychee_workerWorkers not processing jobs:
- Verify
QUEUE_CONNECTION=databaseis set in both API and worker services - Verify
LYCHEE_MODE=workeris set in worker service - Check worker logs:
docker-compose logs -f lychee_worker
Upload issues:
- Verify volume mounts point to correct paths
- Check file permissions on host directories
- Ensure uploads directory exists and is writable
Performance issues:
- Consider adding worker services for background processing
- Verify FrankenPHP is running (check logs for FrankenPHP, not nginx)
- Ensure
QUEUE_CONNECTIONis set for async job processing
Database connection errors:
- Ensure database service name matches
DB_HOSTvalue - Verify database credentials are correct
- Check database service is healthy:
docker-compose ps lychee_db
Configuration changes not applying:
- Remember to restart container after
.envchanges:docker-compose restart lychee - Verify environment variables in docker-compose.yml take precedence over
.envfile
For additional support:
For galleries with thousands of photos:
- Use worker services for background processing
- Scale workers based on your hardware (2-4 workers recommended)
- Use Redis for queue backend instead of database:
environment: - QUEUE_CONNECTION=redis - REDIS_HOST=redis - REDIS_PORT=6379
- Allocate sufficient resources in docker-compose:
deploy: resources: limits: memory: 2G reservations: memory: 1G
The FrankenPHP-powered Version 7 image provides significant performance improvements over Version 6:
- Faster boot times: 4-6ms vs 40-60ms per request
- Better throughput: 3-4x improvement in requests per second
- Lower latency: Reduced response times across all operations
- Memory efficiency: Framework components kept in memory and reused
These improvements are automatic when using Version 7 - no additional configuration required.
- Use Docker secrets for sensitive credentials in production
- Mount
.envas read-only (:ro) to prevent modifications - Use strong passwords for database and admin accounts
- Keep images updated to receive security patches
- Run behind a reverse proxy (nginx, Traefik, Caddy) with TLS
- Restrict network access using Docker networks
- Use specific version tags instead of
latestin production for reproducible deployments