recipe

Retrieval API with Qdrant

An API that stores passages in Qdrant and returns the ones closest to a question, with embeddings computed in the container and no API key. About $21.19 a month.

$21.19 a monthchecked 22 October 2026the codeas markdown

What you build

  • POST /ingest with {"texts": [...]} embeds and stores up to 256 passages.
  • POST /ask with {"question": "...", "limit": 3} returns the closest ones.
  • Both need Authorization: Bearer <token>. GET /healthz does not.

Embeddings come from BAAI/bge-small-en-v1.5, run inside the container with fastembed. There is no embeddings API to pay for, and the model is part of the image, so the first request does not wait for a download.

What Gagarin creates

NameKindSizeHolds
vectorsqdrants, 5 GB diskthe passages and their vectors
authexternalnoneAUTH_TOKEN
apiservicesthe API, at a public HTTPS address

The commands

Run from this directory, with Docker running.

gg init search
gg resource add search/vectors qdrant --storage 5
gg resource add search/auth external --env-file .env.auth
gg ship search/api:8080 --deps vectors --deps auth
gg domain add search/api

Make .env.auth with echo "TOKEN=$(openssl rand -hex 32)" > .env.auth and add it to .gitignore. Gagarin publishes it as AUTH_TOKEN, and the qdrant gives the service VECTORS_URL and VECTORS_API_KEY.

Try it with the address gg domain add printed:

curl -H "Authorization: Bearer <token>" -H 'content-type: application/json' \
  -d '{"texts":["Paris is the capital of France."]}' https://<address>/ingest
curl -H "Authorization: Bearer <token>" -H 'content-type: application/json' \
  -d '{"question":"What is the French capital?"}' https://<address>/ask

What it costs

About $21.19 a month: two size-s containers at $10.22 each and 5 GB of disk at $0.75. A qdrant serving real traffic wants size m, which makes it about $41.63. Traffic is free. Rates: https://gagarin.cloud/pricing

What can go wrong

  • gg ship fails before it builds: Docker is not running. docker info says.
  • The image is about 800 MB, most of it the model and its runtime. The first push takes a minute or two on a slow uplink.
  • /ask returns nothing: nothing was ingested yet, or it went to another project's qdrant. gg status search lists what the project holds.
  • The qdrant keeps a nightly backup for 14 days: gg resource backups search/vectors.

Run it locally

cp .env.example .env    # put a token in it
docker compose up --build

Clean up

gg destroy search

Gagarin emails the project owner a button to approve the deletion. The vectors go with it.