Thank you for your interest in contributing to LibrePhotos! This guide will help you get started with the development process.
- Development Environment Setup
- Docker & Docker Compose
- IDE Recommendations
- Code Quality Standards
- Logging
- How to Open a Pull Request
- Getting Help
- Git - for version control
- Docker and Docker Compose - for running the development environment
- Node.js 22 and Yarn - for frontend development (optional, if developing outside Docker)
- Python 3.11+ - for backend development (optional, if developing outside Docker)
Create a directory for the project and clone the LibrePhotos monorepo. All apps (backend, frontend, mobile, docs) and the deploy configs live in a single repository.
Linux/macOS:
export codedir=~/dev
mkdir -p $codedir
cd $codedir
git clone https://github.com/LibrePhotos/librephotos.git
cd librephotosWindows (PowerShell):
$Env:codedir = "$HOME\dev"
New-Item -ItemType Directory -Force -Path $Env:codedir
Set-Location $Env:codedir
git clone https://github.com/LibrePhotos/librephotos.git
Set-Location librephotosNavigate to the deploy/compose directory and create your .env file:
cd deploy/compose
cp librephotos.env .envEdit the .env file and set these critical variables:
# Path to your photo library (for testing)
scanDirectory=/path/to/your/test/photos
# Path to LibrePhotos data
data=./librephotos/data
# IMPORTANT: Path to the monorepo checkout
codedir=~/dev/librephotosdocker compose -f docker-compose.yml -f docker-compose.dev.yml up -dThis command:
- Builds development images with hot-reload enabled
- Mounts your local source code into the containers
- Starts all required services (backend, frontend, database, proxy)
Access LibrePhotos at: http://localhost:3000
If you add new dependencies to requirements.txt or package.json:
# Rebuild backend
docker compose -f docker-compose.yml -f docker-compose.dev.yml build --no-cache backend
# Rebuild frontend
docker compose -f docker-compose.yml -f docker-compose.dev.yml build --no-cache frontend
# Restart containers
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -dLibrePhotos uses a microservices architecture with four main containers:
| Container | Purpose |
|---|---|
backend |
Django API server, ML models, background jobs |
frontend |
React web application |
proxy |
Nginx reverse proxy, serves static files |
db |
PostgreSQL database |
# View running containers
docker compose ps
# View logs (all containers)
docker compose -f docker-compose.yml -f docker-compose.dev.yml logs -f
# View logs (specific container)
docker compose -f docker-compose.yml -f docker-compose.dev.yml logs -f backend
# Restart a container
docker compose -f docker-compose.yml -f docker-compose.dev.yml restart backend
# Stop all containers
docker compose -f docker-compose.yml -f docker-compose.dev.yml down
# Stop and remove volumes (fresh start)
docker compose -f docker-compose.yml -f docker-compose.dev.yml down -v
# Execute command in container
docker exec -it backend bash
docker exec -it frontend sh
# Run Django management commands
docker exec -it backend python manage.py migrate
docker exec -it backend python manage.py createsuperuser| Aspect | Development (docker-compose.dev.yml) |
Production (docker-compose.yml) |
|---|---|---|
| Source code | Mounted from local filesystem | Built into image |
| Hot reload | ✅ Enabled | ❌ Disabled |
| Debug mode | ✅ DEBUG=1 |
❌ DEBUG=0 |
| Build time | Longer (builds from source) | Fast (pulls pre-built images) |
| Additional tools | pgAdmin available on port 3001 | Minimal |
VS Code is the recommended IDE with excellent Docker and Python support.
Recommended Extensions:
- Python - Python language support
- Pylance - Fast Python language server
- Docker - Docker container management
- Remote - Containers - Develop inside Docker containers
- ESLint - JavaScript/TypeScript linting
- Prettier - Code formatting
Workspace Settings:
The repository includes VS Code settings in deploy/vscode/settings.json that are automatically mounted into the backend container.
Attaching to Backend Container:
For the best development experience, you can attach VS Code directly to the running backend container:
- Install the "Remote - Containers" extension
- Open Command Palette (
Ctrl+Shift+P) - Run "Remote-Containers: Attach to Running Container"
- Select the
backendcontainer - Open the
/codefolder
PyCharm Professional supports Docker interpreters natively:
- Go to Settings → Project → Python Interpreter
- Add Interpreter → On Docker Compose
- Select the
docker-compose.ymlanddocker-compose.dev.ymlfiles - Choose the
backendservice
Any IDE with Python and TypeScript support will work. Key requirements:
- Python 3.11+ interpreter support
- ESLint/Prettier integration for frontend
- Docker integration (optional but helpful)
Linting and Formatting:
We use ruff for linting and formatting (configured in pyproject.toml). The
version is pinned in pyproject.toml (required-version) so that everyone gets
the same findings — install the version from requirements.dev.txt, any other
one refuses to run:
# Inside the backend container
cd /code
pip install "$(grep ^ruff== requirements.dev.txt)"
ruff check .
ruff format .Pre-commit Hooks:
Install pre-commit hooks for automatic formatting:
pip install pre-commit
pre-commit installCode Style:
- Line length: 88 characters
- Use type hints where practical
- Follow PEP 8 naming conventions
- Write docstrings for public functions
Linting and Formatting:
# Inside frontend container or locally
yarn lint:error # Check for errors
yarn lint:warning:fix # Fix linting issuesCode Style:
- Line length: 120 characters
- Use Prettier for formatting (configured in
prettier.config.cjs) - Prefer TypeScript types over interfaces (project convention)
- Use functional components with hooks
- Follow the slice pattern for Redux state management
Before submitting a PR, ensure:
- Code follows the project's style guidelines
- All linting passes without errors
- New features include tests (if applicable)
- Documentation is updated (if needed)
- Commit messages are clear and descriptive
- The PR addresses a single concern/feature
ownphotos.log is what users attach to a bug report, so it is a shared resource: everything you log competes for space with the line that would have explained someone else's crash.
import logging
logger = logging.getLogger(__name__)Every module gets its own logger this way; do not import another module's logger. The logger name is the module path (api.directory_watcher.scan_jobs), so one module or a whole package can be turned up or down on its own with LOG_LEVELS (see below). The records still propagate to the root logger, which owns the handlers, so they land in ownphotos.log and on the console like everything else.
The rule a reviewer actually applies: INFO volume must be O(number of jobs/requests), never O(number of photos). Per-photo, per-file and per-request detail is DEBUG.
| Level | Use it for |
|---|---|
DEBUG |
The per-item detail: this file, this photo, this request. Off by default, so this is the one level where volume may scale with the library. |
INFO |
A job or request started, finished, or took a decision an admin would want to see afterwards. One line per job, not per item. |
WARNING |
Something was skipped, retried or fell back, and the work carried on. A single unreadable photo is a WARNING. |
ERROR |
The job or request failed and the user will notice. |
CRITICAL |
The process cannot run at all - an unwritable log directory, an unreachable database. Rare. |
logger.exception() (an ERROR plus the traceback) belongs where the job actually died. One failed item inside a loop that keeps going is a WARNING - otherwise a folder of corrupt files produces a traceback per photo and buries the real failure.
-
Lazy
%args, never f-strings. The arguments are only formatted if the line is actually written, so aDEBUGcall costs nothing whenLOG_LEVELisINFO. Ruff'sGrules are on and already reject.format(),+concatenation andexc_info=True; the f-string ruleG004is the one exception, muted inpyproject.tomlbecause ~257 call sites predate it and get converted area by area. Do not add new ones.logger.info("job %s: scan finished, %s photos added", job_id, count) # yes logger.info(f"job {job_id}: scan finished, {count} photos added") # no
-
Always carry the identifier somebody would need to follow the line: the job id, the
image_hash, the user id.api/api_util.py:88and the"job %s: ..."lines inapi/autoalbum.pyare the shape to copy. -
No personal data above
DEBUG. Usernames, absolute media paths, captions, LLM prompts, addresses and search terms do not belong atINFOor above - log the user id and theimage_hashinstead. Plenty of existing code predates this rule; do not add more.
Set LOG_LEVEL=DEBUG on the backend container to see the verbose stream, or LOG_LEVELS=api.directory_watcher=DEBUG to see it for one package only (comma-separate several logger=LEVEL pairs).
- Navigate to the repository you want to contribute to on GitHub
- Click the "Fork" button in the top right corner
- Clone your fork locally:
git clone https://github.com/YOUR-USERNAME/librephotos.git
cd librephotos
git remote add upstream https://github.com/LibrePhotos/librephotos.gitAlways create a new branch for your work:
git checkout -b feature/my-awesome-feature
# or
git checkout -b fix/bug-description- Write your code following the code quality standards above
- Test your changes thoroughly
- Commit your changes with descriptive messages:
git add .
git commit -m "feat: add support for XYZ"
# or
git commit -m "fix: resolve issue with ABC"Commit Message Guidelines:
- Use present tense ("add feature" not "added feature")
- Keep the first line under 72 characters
- Reference issues when applicable:
fix: resolve login bug (#123)
git push origin feature/my-awesome-featureThen on GitHub:
- Navigate to your fork
- Click "Compare & pull request"
- Fill out the PR template with:
- Clear description of changes
- Reference to related issues
- Screenshots (for UI changes)
- Testing instructions
- Address reviewer feedback promptly
- Make requested changes in new commits
- Be open to suggestions and discussion
- Discord: Join our Discord server
- GitHub Issues: Report bugs or request features
- Documentation: docs.librephotos.com
- Development Videos: Niaz Faridani-Rad's YouTube channel
Backend (Django):
Use pdb for debugging:
import pdb; pdb.set_trace()Then attach to the container:
docker attach $(docker ps --filter name=backend -q)Press Ctrl+P followed by Ctrl+Q to detach without stopping the container.
Frontend (React):
- Use React DevTools browser extension
- Use Redux DevTools for state debugging
- Enable WDYR, which logs why each
component re-rendered, by setting
VITE_APP_WDYR=trueindeploy/compose/.envand restarting the frontend container. The value must be the lowercase stringtrue.
API Documentation:
After starting LibrePhotos, access the API docs at:
- Swagger: http://localhost:3000/api/swagger
- ReDoc: http://localhost:3000/api/redoc
By contributing to LibrePhotos, you agree that your contributions will be licensed under the MIT License.
Thank you for contributing! 🎉