Imadeo

Documentation

How to get Imadeo running, and what each part of it does once it is.

Install with Docker

Imadeo runs as four containers: the app, Postgres, Redis, and a machine-learning worker. They come up together from one compose file.

# fetch the compose file and a starting configuration
mkdir imadeo && cd imadeo
curl -O https://raw.githubusercontent.com/eranabir/imadeo/master/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/eranabir/imadeo/master/.env.example

# edit .env, then bring the stack up
docker compose up -d

There is no separate web container. The app serves its own interface, so the only port you need to reach is the server's.

Not yet published. The compose file currently builds the images from source rather than pulling them, so this needs a checkout of the repository until the images are published to a registry.

What each service does

ServicePurpose
serverThe API, the background workers, and the web interface.
databasePostgres with pgvector, which stores both the metadata and the image embeddings that make search work.
redisThe job queue. Thumbnails, metadata extraction, face detection and video transcoding all run through it.
machine-learningFace detection and the image/text model behind search. Runs locally; nothing is sent anywhere.

TrueNAS SCALE

SCALE runs ordinary Docker, so the compose file above works as-is. Since 24.10 the apps system is Docker Compose rather than Kubernetes, which means Imadeo installs the same way it does anywhere else.

Written against TrueNAS Community Edition 24.10 (Electric Eel) or later, where the apps system moved to Docker Compose. Earlier releases used Kubernetes and are not covered here.

Create two datasets first

Imadeo needs two, not one. The library is the obvious half; the database is the half people miss, and losing it means the files survive but every album, name and favourite is gone. A parent with two children keeps them together:

DatasetHolds
/mnt/tank/imadeo/libraryYour photos and videos, plus thumbnails and transcodes.
/mnt/tank/imadeo/databasePostgres. Albums, names on faces, favourites — everything you have organised.

Both go in .env. Neither default is safe on SCALE: DB_DATA_LOCATION is a path relative to the compose file, so left alone it puts your database wherever you happened to unpack things.

UPLOAD_LOCATION=/mnt/tank/imadeo/library
DB_DATA_LOCATION=/mnt/tank/imadeo/database

Install

From Apps → Discover → Custom App, choose the option to install via YAML and paste the compose file. Or from a shell, exactly as on any other machine:

mkdir -p /mnt/tank/apps/imadeo && cd /mnt/tank/apps/imadeo
curl -O https://raw.githubusercontent.com/eranabir/imadeo/master/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/eranabir/imadeo/master/.env.example
# set UPLOAD_LOCATION to your dataset, then
docker compose up -d
Keep the compose file and .env on a data pool, not on the boot pool. A SCALE upgrade can reset the boot pool, and anything stored there goes with it.
No catalog app yet. Installing from Apps → Discover with a form for the dataset and ports needs Imadeo published to a container registry first — a catalog entry pulls a tag, it cannot build from source.

Where the library lives

UPLOAD_LOCATION in .env is the setting that matters most, and the one to get right before the first upload. It is the host directory bound into the container, and everything Imadeo writes lives under it.

UPLOAD_LOCATION=/volume1/photos
DirectoryHolds
library/Your original files, untouched.
thumbs/Generated thumbnails and preview sizes.
encoded-video/Transcoded copies for playback in the browser.
profile/Profile pictures.
backups/Database dumps.
upload/Scratch space for uploads in progress.
Originals are stored as ordinary files in ordinary directories. Nothing is repackaged into a proprietary format, so the library remains usable without Imadeo — you can copy it, back it up, or walk away with it.

Moving an existing library

Stop the stack, move the directory, point UPLOAD_LOCATION at its new path, and start again. Paths are stored relative to the library root, so nothing needs rewriting in the database.

docker compose down
mv /volume1/photos /volume2/photos
# set UPLOAD_LOCATION=/volume2/photos in .env
docker compose up -d

First run

Open the server's address in a browser. A server with no accounts shows the registration screen instead of a login, because the only thing that makes sense on an empty server is creating the first administrator.

That first account is the admin. Every account created afterwards — by invitation or directly — is an ordinary user with its own separate library.

Uploading

Upload in the header takes individual files, or a whole folder. Choosing a folder brings its structure across intact rather than flattening everything into one pile.

Uploads are checked against what you already have. If a file matches something in your library, Imadeo says so rather than silently making a second copy — but you can override that and keep the duplicate, which keeps its original filename and gets its own entry.

Once a file lands, work is queued: metadata is read, thumbnails are generated, video is transcoded, faces are detected, and the image is embedded for search. Large uploads finish appearing gradually.

The timeline

Photos is everything you have, grouped by the day it was taken. The rail down the right side lists the years in your library; clicking one jumps to it.

Selecting

  • Click the circle on a photo to select it.
  • Shift-click selects everything between two photos, across day boundaries.
  • Hover a date heading and a circle appears beside it — that takes the whole day at once.
  • With a selection active, the bar at the bottom can favourite, download, or move to trash.

Sorting

The default is date taken. Recently added is the useful one after an upload: a photo shot in 2009 files itself under 2009 and can look like it never arrived, and sorting by when it was uploaded answers that.

Albums and folders

These are different things, deliberately.

  • Folders mirror the directory structure your files arrived in. Upload a tree and it comes across as it was.
  • Albums are sets you assemble yourself. A photo can be in several albums and still sit in its original folder.

Right-clicking a photo, or a selection, offers to add it to an album.

People

Faces are detected as photos arrive and grouped by who they belong to. Nobody is named at first — open People & Pets, click a face, and give it a name. Every photo of that person is grouped from then on.

  • Merge combines two groups that turned out to be the same person.
  • Not this person corrects a wrong match. The photo is kept; only the association is removed, and it will not come back on the next pass.
  • Use as cover picks which photo their thumbnail is cropped from.
  • Hiding a person removes them from the grid without touching any photos.

You can also assign a photo to someone by hand, from the right-click menu, whether or not a face was detected in it.

Duplicates

Duplicates groups photos that are the same picture rather than the same file. A resized copy, a re-saved export and a version that came back through a messaging app all match, even though their bytes differ and no two filenames agree.

Nothing is deleted for you. Each group shows what it found so you can keep the one you want.

Locked

Photos moved to Locked are kept out of the timeline, search results and albums. They are reachable only from that section.

Locked is not encryption. It hides photos from the interface; the files remain readable on disk to anyone with access to the server.

Accounts and invites

Under Settings, an administrator can invite someone by email or create an account directly.

  • Invitation sends a link that opens registration with the address already filled in. It requires email to be configured.
  • Create a user sets up the account and password yourself, with no email involved.

Each account has its own library. People do not see each other's photos unless something is shared with them.

Email

Email is optional. Without it Imadeo works normally, but invitations and address-change confirmations cannot be sent — the invite button stays disabled until a server is configured.

Settings takes ordinary SMTP details from your provider. One field is easy to miss: the public address of your server. Links in outgoing email are built from it, so if it is left pointing at localhost, an invitation will work for you and for nobody else.

# reachable from wherever the recipient opens the link
https://photos.example.com

Backing up

Two things need backing up, and they are not the same:

  • The library directory — your actual photos and videos. This is the part that cannot be regenerated.
  • The database — albums, names on faces, favourites, and everything else you have organised. Losing it means the files survive but the organisation does not.

Thumbnails and transcoded video are derived and can be rebuilt, so they are not worth the backup space.

docker compose exec -T database pg_dump -U "$DB_USERNAME" "$DB_DATABASE_NAME" \
  > imadeo-$(date +%F).sql