# Lumina

Lumina is a self-hosted photo and video library for organizing, searching, and sharing personal media. It runs on your own hardware, stores originals on disk, and provides a responsive web interface for desktop and mobile devices.

> Lumina is under active development. Back up your media and database before upgrading.

## Features

- Automatic photo and video indexing
- Timeline, album, favorites, and archive views
- Search by filename, date, camera, location, and tags
- EXIF metadata and map display
- Duplicate detection using file hashes
- Background thumbnail and preview generation
- Private and password-protected shared albums
- Multiple user accounts
- Optional read-only library mode
- Dark and light themes
- Docker-based deployment
- PostgreSQL database
- Local filesystem or mounted network storage

## Requirements

- Docker Engine 24 or newer
- Docker Compose v2
- At least 2 GB of RAM
- A writable directory for application data
- A local or mounted directory containing your media

For large libraries, SSD storage is recommended for the database and thumbnail cache.

## Installation

Create a project directory:

```bash
mkdir lumina
cd lumina
```

Create a `compose.yaml` file:

```yaml
services:
  app:
    image: ghcr.io/lumina-photos/lumina:latest
    container_name: lumina
    restart: unless-stopped
    depends_on:
      database:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      LUMINA_DATABASE_URL: postgres://lumina:change-me@database:5432/lumina
      LUMINA_MEDIA_ROOT: /media
      LUMINA_DATA_ROOT: /data
      LUMINA_SECRET_KEY: replace-with-a-long-random-value
    volumes:
      - ./data:/data
      - ./photos:/media
    healthcheck:
      test: ["CMD", "/app/lumina", "healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 5

  database:
    image: postgres:17-alpine
    container_name: lumina-database
    restart: unless-stopped
    environment:
      POSTGRES_DB: lumina
      POSTGRES_USER: lumina
      POSTGRES_PASSWORD: change-me
    volumes:
      - ./postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U lumina -d lumina"]
      interval: 10s
      timeout: 5s
      retries: 5
```

Generate a secret key:

```bash
openssl rand -hex 32
```

Replace both instances of `change-me` with the same strong database password, set `LUMINA_SECRET_KEY` to the generated value, and update the media volume if your library is stored elsewhere:

```yaml
volumes:
  - ./data:/data
  - /mnt/storage/photos:/media
```

Start Lumina:

```bash
docker compose up -d
```

Open [http://localhost:8080](http://localhost:8080) and follow the setup wizard to create the administrator account.

## Usage

### Importing media

Files placed in the configured media directory are discovered during a library scan. Lumina does not move, rename, or modify original files.

Start a scan from **Administration → Library → Scan**, or run:

```bash
docker compose exec app lumina scan
```

To watch the media directory for new files:

```yaml
environment:
  LUMINA_WATCH_ENABLED: "true"
```

A scheduled full scan is still recommended for media stored on network filesystems.

### Creating albums

Select one or more items in the library and choose **Add to album**. Albums reference existing media and do not create additional copies of the original files.

### Sharing albums

Open an album, select **Share**, and configure an expiration date or password if needed. Shared links are disabled by default for non-administrator users.

If Lumina is behind a reverse proxy, configure `LUMINA_PUBLIC_URL` so generated links use the correct hostname.

### Updating

Pull the latest image and recreate the containers:

```bash
docker compose pull
docker compose up -d
```

Database migrations run automatically during startup. Review release notes before upgrading across major versions.

### Backups

Back up the following:

- PostgreSQL database
- Lumina data directory
- Original media directory

Create a database dump with:

```bash
docker compose exec -T database pg_dump -U lumina lumina > lumina-backup.sql
```

The data directory contains thumbnails and generated previews. It can be regenerated, but preserving it makes restores significantly faster.

## Configuration

Lumina is configured with environment variables.

| Variable | Default | Description |
| --- | --- | --- |
| `LUMINA_DATABASE_URL` | Required | PostgreSQL connection URL |
| `LUMINA_SECRET_KEY` | Required | Secret used to sign sessions and shared links |
| `LUMINA_MEDIA_ROOT` | `/media` | Directory containing original media |
| `LUMINA_DATA_ROOT` | `/data` | Directory for thumbnails, previews, and temporary files |
| `LUMINA_PUBLIC_URL` | `http://localhost:8080` | Public base URL used in generated links |
| `LUMINA_BIND` | `0.0.0.0:8080` | Address and port for the HTTP server |
| `LUMINA_MEDIA_READ_ONLY` | `true` | Prevent operations that modify original media |
| `LUMINA_WATCH_ENABLED` | `false` | Watch the media directory for changes |
| `LUMINA_SCAN_SCHEDULE` | `0 3 * * *` | Cron expression for automatic library scans |
| `LUMINA_TIMEZONE` | `UTC` | Timezone used for scheduled jobs |
| `LUMINA_LOG_LEVEL` | `info` | Logging level: `debug`, `info`, `warn`, or `error` |
| `LUMINA_TRUST_PROXY` | `false` | Trust forwarded headers from a reverse proxy |
| `LUMINA_MAX_UPLOAD_SIZE` | `10G` | Maximum size of browser uploads |
| `LUMINA_REGISTRATION_ENABLED` | `false` | Allow visitors to create accounts |
| `LUMINA_SHARING_ENABLED` | `true` | Enable public album sharing |
| `LUMINA_TRANSCODE_ENABLED` | `true` | Generate browser-compatible video previews |
| `LUMINA_TRANSCODE_WORKERS` | `1` | Maximum concurrent video transcode jobs |
| `LUMINA_THUMBNAIL_WORKERS` | `2` | Maximum concurrent thumbnail jobs |

Boolean values must be quoted in Compose files:

```yaml
environment:
  LUMINA_MEDIA_READ_ONLY: "true"
  LUMINA_WATCH_ENABLED: "false"
```

### Reverse proxy

When exposing Lumina through a reverse proxy, set:

```yaml
environment:
  LUMINA_PUBLIC_URL: https://photos.example.com
  LUMINA_TRUST_PROXY: "true"
```

The proxy must forward the `Host`, `X-Forwarded-For`, and `X-Forwarded-Proto` headers. TLS should terminate at the proxy.

### Network storage

NFS and SMB mounts should be mounted on the host before starting Lumina, then passed into the container as a bind mount. For safety, mount existing libraries as read-only:

```yaml
volumes:
  - /mnt/nas/photos:/media:ro
```

Filesystem watching may be unreliable on network mounts. Use scheduled scans when changes are not detected automatically.

### Supported formats

Common JPEG, PNG, WebP, GIF, HEIC, TIFF, and RAW photo formats are supported. Video playback depends on browser codec support; Lumina can generate H.264 previews for incompatible source files.

## Troubleshooting

View application logs:

```bash
docker compose logs -f app
```

Check service status:

```bash
docker compose ps
```

Rebuild missing thumbnails:

```bash
docker compose exec app lumina thumbnails rebuild
```

If files do not appear, verify that the container can read the mounted directory:

```bash
docker compose exec app ls -la /media
```

Permission issues are usually caused by the host directory not being readable by the container user.

## Security

Lumina is intended to run behind HTTPS when accessible outside a trusted network. Use strong passwords, keep Docker images updated, disable public registration unless required, and avoid exposing PostgreSQL directly to the internet.

## License

Lumina is released under the GNU Affero General Public License v3.0. See `LICENSE` for details.