# recoll-modern A modern, self-hosted web UI and MCP server on top of [Recoll](https://www.lesbonscomptes.com/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](compose.published.yaml) alongside this guide, or download it from the repository after the publishing branch reaches `main`: ```sh 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: ```sh 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: ```sh 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: ```sh 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: ```sh 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](https://docs.gitea.com/usage/actions/comparison/); 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`](AGENTS.md). It is the single source of truth for commands, workflow, and boundaries. Deeper context lives in [`docs/`](docs/).