Skip to main content

🐋 Docker

Overview​

The deploy/ directory in the monorepo contains the Docker configuration for running LibrePhotos. This includes:

  • deploy/compose/docker-compose.yml - Base configuration (published images), used in both dev and prod
  • deploy/compose/docker-compose.dev.yml - Development overrides (builds from local source, hot reload)
  • deploy/compose/docker-compose.unified.yml - Single-container image with PostgreSQL; reads the same librephotos.env as docker-compose.yml, and api/tests/infra/test_compose_files_in_sync.py keeps the backend environment of the two files equal
  • deploy/compose/docker-compose.e2e.yml - End-to-end test stack (builds from local source)
  • deploy/docker/backend/ - Backend Dockerfile (app layer) and entrypoint.sh, the one entrypoint of the backend, GPU and unified images. Image differences are guarded blocks in it (nvidia-smi for GPU, SERVE_FRONTEND for unified) or ENV defaults in a Dockerfile; api/tests/infra/test_deploy_images.py fails on a second copy
  • deploy/docker/backend/base/ - Backend base image Dockerfile (system dependencies), published separately
  • deploy/docker/backend-gpu/ and deploy/docker/backend-gpu/base/ - CUDA (GPU) equivalents of the backend and its base
  • deploy/docker/frontend/ - Frontend Dockerfiles (production and development)
  • deploy/docker/proxy/ - Nginx reverse proxy configuration
  • deploy/docker/unified/ - Single-container image (Single Container Deployment) with its own standalone Dockerfile; build it locally from the repo root with docker build -f deploy/docker/unified/Dockerfile .

✨ Code Standards​

When modifying Docker files, please ensure:

  1. Cross-platform compatibility: Test on both Linux and macOS/Windows (WSL2)
  2. ARM64 support: Images should build on both AMD64 and ARM64 (Apple Silicon, Raspberry Pi). The GPU images (deploy/docker/backend-gpu/) are the exception — they are AMD64-only by design, because the CUDA base has no ARM64 build. CI opts them out with amd64-only: true (see .github/workflows/_build-image.yml)
  3. Layer optimization: Order commands to maximize Docker layer caching
  4. Security: Don't run services as root when avoidable; use minimal base images

Architecture​

Container Relationships​

┌─────────────┐
│ proxy │ :80
│ (nginx) │ also serves /data, /protected_media,
└──────┬──────┘ /original from its own bind mounts
│
┌──────────┴──────────┐
┌─────▼─────┐ ┌─────▼─────┐
│ frontend │ │ backend │
│ (React) │ │ (Django) │
└───────────┘ └─────┬─────┘
│
┌─────▼─────┐
│ db │
│(Postgres) │
└───────────┘

Development vs Production​

FilePurpose
docker-compose.ymlBase configuration, used in both dev and prod
docker-compose.dev.ymlDevelopment overrides (hot reload, mounted source, pgadmin on :3001)

Making Changes​

Modifying the Backend Dockerfile​

The backend Dockerfile (deploy/docker/backend/Dockerfile) builds the Django application:

cd librephotos/deploy/compose

# Test your changes
docker compose -f docker-compose.yml -f docker-compose.dev.yml build backend
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d backend

# Check logs for errors
docker compose logs -f backend

Modifying the Backend Base Image​

The backend image is built in two tiers. deploy/docker/backend/Dockerfile starts FROM reallibrephotos/librephotos-base:${BASE_TAG} (BASE_TAG defaults to dev), then pip-installs requirements.txt and copies in apps/backend/. The requirements files are copied and installed before the source, so a change that only touches Python code reuses the cached dependency layers. The only system-level dependencies are Python and perl (for the ExifTool wheel), in the separate deploy/docker/backend/base/Dockerfile; libvips, LibRaw, ExifTool, ffmpeg, libpq and insightface come from pip as prebuilt wheels (.github/workflows/prebuilt-wheels.yml). There is no PyTorch: the ML models run on ONNX Runtime, which is a plain pip package in requirements.txt.

The base is pulled, not built: docker compose build backend, even with --no-cache, will not rebuild it. To test a change to the base locally, build and tag it yourself first, then rebuild the backend:

# from the monorepo root
docker build -t reallibrephotos/librephotos-base:dev deploy/docker/backend/base

# then rebuild the app layer (from deploy/compose/)
docker compose -f docker-compose.yml -f docker-compose.dev.yml build backend

The base image is published to Docker Hub by .github/workflows/image-backend-base.yml, which runs only on pushes to dev that touch deploy/docker/backend/base/**. The GPU build has the same split — deploy/docker/backend-gpu/base/Dockerfile produces reallibrephotos/librephotos-base-gpu:dev — and the unified image (deploy/docker/unified/) also builds FROM the CPU base, so a base change affects it too. A release does not rebuild either base: it promotes the current :dev to :<release-tag>, and the release builds of the backend, GPU and unified images pass BASE_TAG=<release-tag>, so a published release keeps the base it shipped with.

Modifying the Frontend Dockerfile​

The frontend has two Dockerfiles:

  • deploy/docker/frontend/Dockerfile - Production build (multi-stage: node:24-slim builds dist/, then halverneus/static-file-server serves it on port 3000 behind the nginx proxy)
  • deploy/docker/frontend/Dockerfile.dev - Development build (hot reload)

Modifying the Proxy (Nginx)​

The proxy configuration is in deploy/docker/proxy/nginx.conf. After changes:

docker compose -f docker-compose.yml -f docker-compose.dev.yml build proxy
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d proxy

Environment Variables​

Environment variables are loaded from the .env file. Key variables for Docker:

VariablePurposeDefault
tagDocker image tag to uselatest
httpPortHost port for the application3000
scanDirectoryPath to photo library-
dataPath to persistent data-

The dev overlay bind-mounts apps/backend, apps/frontend and packages (the frontend compiles the shared packages/api-client from source) by path relative to deploy/compose/, so there is no environment variable for the source-code location.

Testing Changes​

Before submitting a PR, from deploy/compose/. Include the dev overlay on every command — the base docker-compose.yml uses published images and defines no build: sections, so docker compose build on its own rebuilds nothing and starts the images pulled from Docker Hub instead of your change:

  1. Clean build: docker compose -f docker-compose.yml -f docker-compose.dev.yml build --no-cache (this does not refresh the base image — see Modifying the Backend Base Image)
  2. Fresh start: docker compose -f docker-compose.yml -f docker-compose.dev.yml down -v && docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
  3. Check all services: docker compose -f docker-compose.yml -f docker-compose.dev.yml ps (all should be "healthy" or "running")
  4. Test basic functionality: Upload a photo, trigger a scan, verify it works
note

On a fresh stack, uploads are off by default: librephotos.env ships without an allowUpload entry, so docker-compose.yml passes ALLOW_UPLOAD=false and the upload button is hidden in the UI entirely. Enable it by adding allowUpload=true to your .env before the first start, or by turning on Allow uploads under the app's Admin → Site Settings.

GPU Support​

The deploy/docker/backend-gpu/ directory contains the GPU-enabled Dockerfile for NVIDIA CUDA support. There is no compose overlay for GPU — the image is built and published separately as reallibrephotos/librephotos-gpu by .github/workflows/image-backend-gpu.yml.

To build it locally, run from the monorepo root (the Dockerfile's build context is the repo root, not deploy/compose/):

docker build -f deploy/docker/backend-gpu/Dockerfile \
--build-arg IMAGE_TAG=dev \
-t reallibrephotos/librephotos-gpu:dev .

This pulls reallibrephotos/librephotos-base-gpu:dev as its base rather than building it.

note

The GPU images are built for linux/amd64 only; ARM is not supported. Do not spend effort making these Dockerfiles ARM64-compatible.

To run it, point the backend service in deploy/compose/docker-compose.yml at the GPU image and add a device reservation — see Utilizing GPU Acceleration.

Ensure you have:

  • NVIDIA drivers installed
  • nvidia-container-toolkit configured
  • Docker configured to use the nvidia runtime

Common Issues​

Image Size​

Keep images small by:

  • Using multi-stage builds
  • Cleaning apt/pip caches in the same layer
  • Using .dockerignore to exclude unnecessary files

Build Performance​

  • Order Dockerfile commands from least to most frequently changed
  • Copy dependency files before source code for better caching
  • Use --parallel flag for multi-service builds

Volume Permissions​

If you encounter permission issues with mounted volumes on Linux:

# Check the UID/GID of files in the container
docker exec -it backend ls -la /data

# You may need to adjust ownership
sudo chown -R $(id -u):$(id -g) ./librephotos/data