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.
What each service does
| Service | Purpose |
|---|---|
server | The API, the background workers, and the web interface. |
database | Postgres with pgvector, which stores both the metadata and the image embeddings that make search work. |
redis | The job queue. Thumbnails, metadata extraction, face detection and video transcoding all run through it. |
machine-learning | Face 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.
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:
| Dataset | Holds |
|---|---|
/mnt/tank/imadeo/library | Your photos and videos, plus thumbnails and transcodes. |
/mnt/tank/imadeo/database | Postgres. 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
.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.
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
| Directory | Holds |
|---|---|
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. |
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.
Search
The header search field looks at filenames, descriptions and dates. The icon at its right opens the advanced options.
| Search type | Matches |
|---|---|
| Context | What a photo shows. Describe the picture — "a dog on a beach" — and it is matched against the image itself, with no tags or filenames involved. |
| File name or extension | The original filename, or a file type such as .mov. |
| Description | Text you have written on a photo. |
| Album or folder | The name of an album or folder. |
Any of these can be narrowed to a date range, or to particular people.
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.
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 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