Imadeo

Documentation

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

Install with Docker

Imadeo runs as six containers: web, API, background processing, Postgres, Redis, and machine learning. They come up together from one compose file.

Download only the Docker installation files and keep them together:

mkdir imadeo && cd imadeo
curl -fsSLO https://raw.githubusercontent.com/eranabir/imadeo/master/docker/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/eranabir/imadeo/master/docker/generate-secrets.sh
curl -fsSL https://raw.githubusercontent.com/eranabir/imadeo/master/docker/.env.example -o .env.example
chmod +x generate-secrets.sh
./generate-secrets.sh
docker compose up -d

The generator creates .env with cryptographically random database, Redis, JWT and Locked-folder keys. It keeps any value that was already configured, so it is safe to run again. Media, database files, and model files stay in this installation folder by default.

Imadeo exposes one LAN/VPN port, 1111, for both the web app and mobile API. It does not ship a public reverse proxy.

Remote access with a reverse proxy

On a LAN or VPN, open http://<server-ip>:1111 and connect the mobile app to that same address. For internet access, put a reverse proxy in front of web port 1111. The proxy terminates HTTPS and must preserve Host, X-Real-IP, X-Forwarded-For, and X-Forwarded-Proto; allow uploads as large as Imadeo permits.

photos.example.com {
  reverse_proxy http://192.168.1.20:1111
}

With Nginx Proxy Manager or a custom Nginx proxy, add these advanced directives so large files are not reported as a 504 while the NAS is still verifying them:

client_max_body_size 50G;
proxy_request_buffering off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;

Set the public HTTPS address in .env:

PUBLIC_URL=https://photos.example.com
LOCAL_HTTP_ENABLED=true

Forward only the proxy’s HTTPS port through the router. Never expose 1111 directly. A raw WAN IP cannot use a normally trusted certificate, so remote access needs a domain/DDNS name or a VPN.

What each service does

ServicePurpose
webThe browser interface and same-origin proxy to the API.
serverThe API. It stores originals and remains responsive while background work runs elsewhere.
workerReads metadata, creates thumbnails, optimises videos and schedules local analysis with bounded resources.
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/dataOne isolated media tree for every registered user.
/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/data
DB_DATA_LOCATION=/mnt/tank/imadeo-postgres

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 -fsSLO https://raw.githubusercontent.com/eranabir/imadeo/master/docker/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/eranabir/imadeo/master/docker/generate-secrets.sh
curl -fsSL https://raw.githubusercontent.com/eranabir/imadeo/master/docker/.env.example -o .env.example
chmod +x generate-secrets.sh
./generate-secrets.sh
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/imadeo-data
DirectoryHolds
users/<user-id>/library/That user's original files, untouched.
users/<user-id>/thumbs/That user's generated thumbnails and previews.
users/<user-id>/encoded-video/That user's transcoded playback copies.
users/<user-id>/profile/That user's profile picture.
backups/Database dumps.
users/<user-id>/upload/That user's 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/imadeo-data /volume2/imadeo-data
# set UPLOAD_LOCATION=/volume2/imadeo-data 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.

The untouched original is committed before an upload reports success. The browser supplies a small temporary preview, so new photos can appear without waiting for the server. After the upload batch and recent foreground activity finish, durable jobs read metadata and create canonical thumbnails. Video optimisation, search and People & Pets then use a one-at-a-time heavy lane; they never compete with thumbnail creation. Restarting the worker does not lose queued work.

API and processing have separate CPU and memory limits. The defaults suit a small NAS; larger servers can raise WORKER_CPU_LIMIT, WORKER_MEMORY_LIMIT and JOB_BACKGROUND_CONCURRENCY. Settings → Processing shows whether the worker is active, waiting for uploads, or offline.

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.

Sharing

Every user has a private library. Nothing appears in another user’s library unless its owner chooses to share it. The Sharing section collects everything that has been shared with you without mixing it into your own photos.

  • Photos can be shared directly from the selection or photo menu.
  • Albums can be shared with other users on the same server.
  • Folders share their full subtree, including its albums and photos, as read-only.

Recipients can view and download shared content but cannot rename, move, delete, organise, or re-share it. Locked photos, albums, and folders are never shareable; locking content also revokes existing shares.

People & Pets

Faces are detected as photos arrive and grouped by who they belong to. Cats and dogs are recognised separately, so they appear in the Pets tab rather than among people. Nobody is named at first — open People & Pets, click a person or pet, and give it a name. Every photo of that subject is grouped from then on.

  • Merge combines two groups that turned out to be the same person or pet.
  • Not this person or pet 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 or pet 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.

New uploads are analysed automatically. Administrators can use Settings → Recognition → Scan again to run the current library through recognition again after changing settings or correcting older results. If a photo is moved to Trash, it is removed from the group immediately and a surviving photo becomes the group cover.

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.

Users and invitations

Under Settings → Users, an administrator can invite someone by email or create a user 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