Local Gallery is a LAN-friendly media browser built with:
axum backendIt is designed to serve a local media directory over your local network and browse it from desktop or mobile devices.
name, date, or sizecompact, comfortable, largeThe current codebase no longer serves the frontend from one flat full-library media payload.
It now works like this:
This is intended to scale better for large libraries than rescanning and returning the whole tree on every page load.
The backend can now run in two frontend modes:
FRONTEND_MODE=svelte: Rust serves the API/media/thumbnail routes, and SvelteKit serves the UIFRONTEND_MODE=axum: Rust also serves an Axum-rendered HTML UI directly on the backend portThe default is svelte.
server/ Rust backendweb/ SvelteKit frontendmedia/ optional local media folder for developmentdocker-compose.yml combined container setupThe Rust backend has been refactored into smaller modules:
server/src/main.rs: startup and router wiringserver/src/config.rs: env parsing, runtime config, frontend mode, sort/view enumsserver/src/models.rs: shared API/index data typesserver/src/state.rs: shared app stateserver/src/indexer.rs: media scanning, index building, watcher refreshserver/src/handlers/api.rs: /api/* handlersserver/src/handlers/assets.rs: /media/* and /thumbs/* handlersserver/src/responses.rs: folder response assemblyserver/src/paths.rs: path sanitizing, breadcrumbs, URL helpersserver/src/sorting.rs: folder/media sortingserver/src/thumbnails.rs: thumbnail generation and fallback SVG responsesserver/src/frontend/: Axum SSR frontend renderer and styles66779091Local development:
Docker:
0.0.0.0 is only for binding the server. It is not a browser URL.
Examples:
0.0.0.0:6677http://localhost:9091http://192.168.1.25:9091If another device opens the frontend using your machine's LAN IP, the frontend is already set up to use that same host for media and thumbnail URLs by default unless you explicitly override it.
mime_guessimage/* and video/* are indexedThumbnail cache location:
<system temp dir>/local-gallery-thumbnails/<hash-of-media-root>
Examples:
/tmp/local-gallery-thumbnails/<hash>/var/folders/.../T/local-gallery-thumbnails/<hash>Copy the backend env file:
cp server/.env.example server/.env
Edit server/.env and set the media folder you want to serve:
BIND_ADDR=0.0.0.0:6677
MEDIA_ROOT=/absolute/path/to/your/media/folder
FRONTEND_MODE=svelte
CORS_ALLOW_ORIGIN=*
Example:
MEDIA_ROOT=/Users/yourname/Pictures/ScreenShots
On Windows:
MEDIA_ROOT=D:/Pictures/ScreenShots
You can also override the media path from the command line. The CLI argument takes priority over .env:
cd server
cargo run -- /absolute/path/to/your/media/folder
cd server
cargo run
Same machine:
http://localhost:6677/api/health
Another device on the same network:
http://192.168.1.25:6677/api/health
If you want the Rust server to render the UI directly, set:
FRONTEND_MODE=axum
Then run:
cd server
cargo run
Open:
http://localhost:6677http://192.168.1.25:6677Notes:
0.0.0.0:6677localhost or your real LAN IP, not 0.0.0.0cd web
npm install
You usually do not need a frontend env file for local LAN use, because the frontend derives the public API host from the browser request.
If you want to override it explicitly:
cp web/.env.example web/.env
Useful frontend env values:
INTERNAL_API_BASE_URL: server-side fetch URL used by SvelteKitPUBLIC_API_BASE_URL: explicit browser-facing backend URL overridecd web
npm run dev
Same machine:
http://localhost:9091
Another device on the LAN:
http://192.168.1.25:9091
Use this only when FRONTEND_MODE=svelte.
The compose file can run both services together, but you must update the media volume path for your machine first.
Open docker-compose.yml and change the server.volumes entry.
Current example:
volumes:
- /Users/s_mash/Pictures/ScreenShots:/app/media:ro
Replace it with a real path on your machine.
macOS/Linux example:
volumes:
- /Users/yourname/Pictures/ScreenShots:/app/media:ro
Windows short syntax:
volumes:
- "D:/Pictures/ScreenShots:/app/media:ro"
Windows long syntax:
volumes:
- type: bind
source: D:/Pictures/ScreenShots
target: /app/media
read_only: true
The long syntax is usually safer on Windows because it avoids drive-letter parsing issues.
The current docker-compose.yml contains:
PUBLIC_API_BASE_URL: http://192.168.1.10:6677
Set this to your actual LAN IP, or remove it if you want the frontend to derive the public host from the browser request.
docker compose up --build
Same machine:
http://localhost:9091
Another device on the LAN:
http://192.168.1.25:9091
Current folder sorting is stored in the URL query string.
Available sort fields:
namedatesizeDirection:
ascdescAvailable view sizes:
compactcomfortablelargeThis is also stored in the URL query string.
Mobile behavior:
compact: 3 items per rowcomfortable: 2 items per rowlarge: 1 item per rowCurrent browser-facing backend routes:
GET /api/healthGET /api/folderGET /api/folder/{path}GET /media/{path}GET /thumbs/{path}When FRONTEND_MODE=axum, the backend also serves:
GET /GET /{path}/api/folder and /api/folder/{path} support:
sort=name|date|sizedir=asc|descoffset=<number>limit=<number>These are used by the current SvelteKit folder route and infinite scroll behavior.
Download uses the same media route with:
/media/{path}?download=true
Backend:
cd server
cargo check
cargo run
Backend with Axum SSR:
cd server
FRONTEND_MODE=axum cargo run
Frontend:
cd web
npm install
npm run dev
npm run check
Docker:
docker compose up --build
docker compose down
The app now handles large libraries better than the original implementation because:
Set:
BIND_ADDR=0.0.0.0:6677
FRONTEND_MODE=axum
Then run:
cd server
cargo run
Open from another device using:
http://<your-lan-ip>:6677
Do not use 0.0.0.0 in the browser URL.
Check:
0.0.0.0:6677localhost6677 and 9091too many colonsUse quoted short syntax:
- "D:/Pictures/ScreenShots:/app/media:ro"
Or use long bind syntax:
- type: bind
source: D:/Pictures/ScreenShots
target: /app/media
read_only: true
This is expected in the current version.
SVG files are still indexed as media, but the current Rust thumbnail generator does not rasterize SVG, so the server returns a fallback thumbnail instead.
The frontend uses Vidstack, but the backend still serves original files directly without advanced range/streaming optimization. Large-video seeking can still be improved later.