recoll-modern
A modern, self-hosted web UI and MCP server on top of Recoll full-text search, with support for multiple independent collections.
Status: spec 001's authenticated search API and Docker deployment have completed acceptance and review checks. The web UI and MCP server are later specs.
Published Docker image
The image is gitea.lucaswinther.info/lucasw89/recoll-modern for linux/amd64. It currently contains
the authenticated search API and Recoll; the web UI and MCP server are later specs. :main follows
the main branch. A release tag such as :v1.2.3 pins a specific release; :latest follows the newest
version tag. Use a version tag when you want a predictable update. The image records its source,
revision and version in OCI labels (docker image inspect IMAGE).
Run the pulled image with Compose
Save compose.published.yaml alongside this guide, or download it from the
repository after the publishing branch reaches main:
curl -fL -o compose.published.yaml \
https://gitea.lucaswinther.info/lucasw89/recoll-modern/raw/branch/main/compose.published.yaml
If the package is private, log in to the Gitea registry first. Use your Gitea username and a personal access token with package Read permission as the Docker password:
docker login gitea.lucaswinther.info
Save the deployment settings in a private .env file in the same directory as the Compose file. This
keeps the chosen image tag across shells. :main is available after a successful main-branch publish,
even before the first version tag:
umask 077
cat > .env <<'EOF'
RECOLL_MODERN_DOCUMENTS=/srv/documents
RECOLL_MODERN_ADMIN_USERNAME=admin
RECOLL_MODERN_ADMIN_PASSWORD=use-a-long-unique-password
RECOLL_MODERN_IMAGE=gitea.lucaswinther.info/lucasw89/recoll-modern:main
EOF
docker compose -f compose.published.yaml pull
sudo install -d -m 0700 -o 10001 -g 10001 data/service-state data/recoll-index
docker compose -f compose.published.yaml up -d --no-build
docker compose -f compose.published.yaml ps
docker compose -f compose.published.yaml logs -f app
Once a version appears in Gitea Packages, edit RECOLL_MODERN_IMAGE in .env to its tag, such as
gitea.lucaswinther.info/lucasw89/recoll-modern:v1.2.3, to pin that release. Omit
RECOLL_MODERN_IMAGE to use :latest after the first version tag is published. The image is published
only after the CI checks pass; a tag that has not been published cannot be pulled. To update, set the
desired tag and run docker compose -f compose.published.yaml pull followed
by docker compose -f compose.published.yaml up -d --no-build again. Keep the same Compose file
directory so the local data paths are reused.
For a trusted local HTTP trial with docker run, export the settings for that shell:
export RECOLL_MODERN_DOCUMENTS=/srv/documents
export RECOLL_MODERN_ADMIN_USERNAME=admin
export RECOLL_MODERN_ADMIN_PASSWORD='use-a-long-unique-password'
export RECOLL_MODERN_IMAGE=gitea.lucaswinther.info/lucasw89/recoll-modern:main
docker pull "$RECOLL_MODERN_IMAGE"
sudo install -d -m 0700 -o 10001 -g 10001 data/service-state data/recoll-index
docker run -d --name recoll-modern --restart unless-stopped -p 127.0.0.1:8000:8000 \
-e RECOLL_MODERN_ADMIN_USERNAME -e RECOLL_MODERN_ADMIN_PASSWORD \
-e RECOLL_MODERN_HOST=0.0.0.0 -e RECOLL_MODERN_PORT=8000 \
-e RECOLL_MODERN_DEVELOPMENT_MODE=true \
--mount "type=bind,src=$RECOLL_MODERN_DOCUMENTS,dst=/documents,readonly" \
--mount "type=bind,src=$PWD/data/service-state,dst=/var/lib/recoll-modern" \
--mount "type=bind,src=$PWD/data/recoll-index,dst=/var/lib/recoll" \
"$RECOLL_MODERN_IMAGE"
Build locally
The original compose.yaml still builds from this repository:
export RECOLL_MODERN_DOCUMENTS=/srv/documents
export RECOLL_MODERN_ADMIN_USERNAME=admin
export RECOLL_MODERN_ADMIN_PASSWORD='use-a-long-unique-password'
docker compose up -d --build
The service listens on host port 8000 by default; set RECOLL_MODERN_PUBLISH_PORT to change it. The
document directory is mounted read-only. SQLite, the membership map and generated Recoll configuration
live in ./data/service-state; the index lives in ./data/recoll-index, relative to the Compose file
directory. Both directories must be writable by container UID/GID 10001:10001; create them with the
command above before starting either deployment. Back up both directories together and keep a separate
copy of the documents. Container replacement does not delete these bind-mounted directories. If you
already deployed with the earlier named volumes, copy both volumes' contents into these directories
while the old container is stopped, preserving ownership, before starting the new configuration. Do not
start against empty directories in place of existing data.
On first boot, the API is available but /health can return 503 with a Recoll database-open error until
a membership or reindex job has built the first index. Log in through POST /auth/login, create a
collection, then add file or directory sources through POST /collections/{id}/memberships. Mutations
return a durable job ID; inspect GET /jobs/{id} for completion or file errors. POST /jobs/reindex
refreshes the index without deleting it. The login seed applies only when no administrator exists;
changing the environment value later does not rotate an existing password. Protect and back up
./data/service-state.
Production sessions use Secure cookies, so serve the API over HTTPS. For a trusted local HTTP trial,
set RECOLL_MODERN_DEVELOPMENT_MODE=true; do not use that setting on a public endpoint. Set
RECOLL_MODERN_ROOT_PATH=/recoll when hosting under a path prefix, and make the reverse proxy preserve
that path. RECOLL_MODERN_TRUSTED_PROXIES accepts comma-separated IP networks; only forwarded host,
scheme and prefix headers from those networks are honored. The health URL follows the prefix, for
example /recoll/health.
To run the generated-corpus container checks, start Compose with a test document containing the unique
word zebraplatypus, set RECOLL_MODERN_TEST_CORPUS_FILE to its path relative to the document root,
and run RECOLL_MODERN_REQUIRE_DOCKER=1 uv run pytest tests/integration. Ordinary host test runs skip
these checks when Compose is not running. Recoll commands run only inside the container.
Publishing from Gitea
Enable repository Actions and use a runner with the ubuntu-latest label, Python 3, uv setup support,
and a working Docker CLI/daemon. Run jobs in isolated containers so a later job cannot read another
job's home directory. Set the repository Actions secret REGISTRY_TOKEN to a personal access token
with package Read & Write permission. The workflow uses the triggering Gitea account as the
registry username by default. If the token belongs to a different account, set REGISTRY_USERNAME
to that account in repository Actions secrets. Gitea currently requires a PAT for Actions package
publishing; the built-in job token does not publish
OCI packages. The workflow checks pull requests without registry credentials and publishes only after
checks pass on main or a full vX.Y.Z tag. No server deployment is performed by CI.
Working here (human or agent): read AGENTS.md. It is the single source of truth for
commands, workflow, and boundaries. Deeper context lives in docs/.