CreateBucket icon
Request icon
If icon
List icon
OutputValues icon
Log icon
Loop icon
Download icon
Parse icon
IngestDocument icon
Ollama icon
Qdrant icon
Assert icon
Set icon
Trigger icon
Schedule icon

Self-refreshing local RAG knowledge base from a MinIO bucket

Sync a MinIO bucket into Qdrant with Tika and Ollama in Kestra. Handles new, changed and deleted files, and heals failed runs. Local, no API keys.

Categories
AI

Most RAG demos index a folder once and never look at it again. Documents change, get deleted and sometimes fail to parse, and the index quietly drifts from the source. This flow keeps a Qdrant collection in sync with a MinIO bucket, so dropping, replacing or removing a file is all it takes to update the knowledge base. Text extraction (Apache Tika), embeddings (Ollama) and storage all run on your machine, so documents never leave it.

Pair it with local-rag-knowledge-base-ask, which answers questions over the same collection with a local LLM and refuses the ones your documents do not cover.

This blueprint was created by kruxarth.

How it works

  1. on_document_change (io.kestra.plugin.minio.Trigger) polls the bucket every 30 seconds and fires for files that are new or whose ETag changed (on: CREATE_OR_UPDATE). Files stay in the bucket (action: NONE), because the bucket is the source of truth. hourly_reconciliation (Schedule) runs the same flow every hour without any file event.
  2. ensure_bucket and create_collection_if_missing (If) create the bucket, the Qdrant collection (768 dimensions, cosine) and a keyword index on source on the first run, so there is nothing to set up by hand.
  3. plan_sync compares list_bucket with the sources that have chunks in Qdrant (indexed_sources, a facet query) and outputs two lists:
    • deleted: sources with chunks but no file. The MinIO trigger cannot see deletions, so this is how they are caught.
    • to_index: files reported by the trigger, plus any file still missing from the index.
  4. purge_deleted_documents (If) removes the chunks of deleted files in one Qdrant call.
  5. index_documents (Loop) processes one file at a time:
    • download (io.kestra.plugin.minio.Download) and extract_text (io.kestra.plugin.tika.Parse) turn PDF, DOCX, Markdown and anything else Tika reads into plain text.
    • has_text (If) skips files with no text, such as scanned PDFs or corrupt files, with a WARN log, so an empty or broken file never replaces a good version.
    • delete_previous_chunks removes the old version of the file, embed_and_store (io.kestra.plugin.ai.rag.IngestDocument) splits it into 1,000-character chunks and embeds them with nomic-embed-text, tagging each chunk with its source key, and assert_document_indexed checks that chunks really landed in Qdrant.
  6. publish_manifest writes the indexed files and their chunk counts to the knowledge_base_manifest KV entry.

What you get

  • A vector index that follows the bucket: new and replaced files are indexed within about 30 seconds, and deleted files are purged by the next run, at the latest by the hourly one.
  • Self-healing: a file that failed to index (Ollama down, a crash mid-run) is picked up again by the next run, with no manual restart.
  • One bad file never blocks the others: it is skipped with a log line that names it.
  • Retries with exponential backoff on embedding, an ERROR log naming the failing task, and a manifest you can read from the KV Store.

Who it's for

  • AI practitioners who want a RAG index that stays correct without re-indexing everything.
  • Platform teams who cannot send internal documents to a hosted embedding API.

Why orchestrate this with Kestra

  • The trigger keeps per-file state, so only changed files are processed, and concurrency queues runs so two of them never index the same file at once.
  • Every file is its own loop iteration with its own logs, so you can see exactly which document failed and why.
  • Retries, scheduling and alerting are declared in YAML instead of being re-implemented in a script.

Prerequisites

MinIO, Qdrant and Ollama, reachable from Kestra:

docker run -d --name minio -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin cgr.dev/chainguard/minio server /data --console-address :9001
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 -e QDRANT__SERVICE__API_KEY=change-me qdrant/qdrant
docker run -d --name ollama -p 11434:11434 ollama/ollama
docker exec ollama ollama pull nomic-embed-text

The flow's variables point at localhost. If Kestra itself runs in Docker, replace localhost with host.docker.internal (add extra_hosts: ["host.docker.internal:host-gateway"] to the Kestra service on Linux).

Secrets

  • MINIO_ACCESS_KEY and MINIO_SECRET_KEY: credentials with read and list access to the bucket, and permission to create it on the first run.
  • QDRANT_API_KEY: the value of QDRANT__SERVICE__API_KEY (the Qdrant plugin requires one).

Quick start

  1. Start the services above and add the three secrets.
  2. Execute the flow once. It creates the knowledge-base bucket and the knowledge_base collection, and publishes an empty manifest. Until then, the trigger logs that the bucket does not exist.
  3. Upload a few PDF, DOCX or Markdown files to the bucket, for example from the MinIO console at http://localhost:9001. Within 30 seconds an execution indexes them: open KV Store in the company.ai namespace to see knowledge_base_manifest with one entry per file.
  4. Replace a file, then delete another, and watch the manifest follow.

Things to know

  • Files without extractable text are logged as WARN on every run until they are fixed or removed. To read scanned PDFs, install Tesseract on the worker and set ocrOptions on extract_text.
  • An updated file is missing from the index for the few seconds between delete_previous_chunks and embed_and_store.
  • Changing embedding_model means a different vector size: update embedding_dimension, delete the collection and let the next run rebuild it.

How to extend

  • Watch only part of the bucket with prefix or regexp on the trigger, or run one collection per team.
  • Send the log_failure message to Slack or email instead of the logs.
  • Swap Ollama for a hosted embedding provider, or Qdrant for another io.kestra.plugin.ai.embeddings store.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.