> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Qdrant

export const DynamicCodeBlock = ({language = 'yaml', filename, icon, lines, wrap, expandable, highlight, focus, children}) => {
  const STORAGE_KEY = 'upsun_versions_cache';
  const COMPOSABLE_STORAGE_KEY = 'upsun_composable_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = 'https://meta.upsun.com/images';
  const COMPOSABLE_API_URL = 'https://meta.upsun.com/composable';
  const DEBUG_PREFIX = '[DynamicCodeBlock cache]';
  const [versionData, setVersionData] = useState(null);
  const [versionError, setVersionError] = useState(false);
  const [composableData, setComposableData] = useState(null);
  const [composableError, setComposableError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      let cachedData = null;
      let cachedEtag = null;
      if (typeof localStorage !== 'undefined') {
        try {
          const cached = localStorage.getItem(STORAGE_KEY);
          if (cached) {
            const parsed = JSON.parse(cached);
            cachedData = parsed?.data || null;
            cachedEtag = parsed?.etag || null;
            if (cachedData && Date.now() - parsed.timestamp < CACHE_TTL) {
              return cachedData;
            }
          }
        } catch (err) {
          console.error('Failed to load from cache:', err);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      console.debug(`${DEBUG_PREFIX} revalidating`, {
        storageKey: STORAGE_KEY,
        hasCachedData: Boolean(cachedData),
        hasCachedEtag: Boolean(cachedEtag)
      });
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        console.debug(`${DEBUG_PREFIX} revalidated (304)`, {
          storageKey: STORAGE_KEY
        });
        if (typeof localStorage !== 'undefined') {
          try {
            const etag = response.headers.get('etag') || cachedEtag;
            localStorage.setItem(STORAGE_KEY, JSON.stringify({
              data: cachedData,
              etag,
              timestamp: Date.now()
            }));
          } catch (err) {
            console.error('Failed to refresh cache metadata:', err);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      console.debug(`${DEBUG_PREFIX} refreshed (200)`, {
        storageKey: STORAGE_KEY,
        etag
      });
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (err) {
          console.error('Failed to cache data:', err);
        }
      }
      return data;
    };
    fetchData().then(data => setVersionData(data)).catch(err => console.error('Failed to fetch version data:', err));
  }, []);
  const findHighestVersion = versionsMap => {
    if (!versionsMap || Object.keys(versionsMap).length === 0) return null;
    const entries = Object.entries(versionsMap);
    const active = entries.filter(([, v]) => v.upsun && v.upsun.status === 'supported' || v.upsun && v.upsun.status === 'deprecated');
    const candidates = active.length > 0 ? active : entries;
    let [highestName] = candidates[0];
    for (let i = 1; i < candidates.length; i++) {
      const [currentName] = candidates[i];
      const cp = currentName.split('.').map(Number);
      const hp = highestName.split('.').map(Number);
      for (let j = 0; j < Math.max(cp.length, hp.length); j++) {
        if ((cp[j] || 0) > (hp[j] || 0)) {
          highestName = currentName;
          break;
        } else if ((cp[j] || 0) < (hp[j] || 0)) {
          break;
        }
      }
    }
    return highestName;
  };
  const getVersion = (lang, requestedVersion = 'latest') => {
    if (lang === 'composable') {
      if (!composableData || !composableData.versions || Object.keys(composableData.versions).length === 0) return null;
      if (requestedVersion && requestedVersion !== 'latest') {
        return (requestedVersion in composableData.versions) ? requestedVersion : null;
      }
      return findHighestVersion(composableData.versions);
    }
    if (!versionData) return null;
    const imageData = versionData[lang];
    if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
      return null;
    }
    if (requestedVersion && requestedVersion !== 'latest') {
      return (requestedVersion in imageData.versions) ? requestedVersion : null;
    }
    return findHighestVersion(imageData.versions);
  };
  let code = typeof children === 'string' ? children : String(children || '');
  const codeLines = code.split('\n');
  while (codeLines.length > 0 && codeLines[0].trim() === '') codeLines.shift();
  while (codeLines.length > 0 && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
  if (codeLines.length > 0) {
    const indents = codeLines.filter(line => line.trim().length > 0).map(line => line.match(/^[ \t]*/)[0].length);
    const minIndent = Math.min(...indents);
    code = codeLines.map(line => line.slice(minIndent)).join('\n');
  }
  code = code.replace(/\{\{version:(.*?)\}\}/g, (match, params) => {
    const parts = params.split(':');
    const lang = parts[0];
    const ver = parts[1] || 'latest';
    const isComposable = lang === 'composable';
    const hasError = isComposable ? composableError : versionError;
    const dataReady = isComposable ? composableData !== null : versionData !== null;
    if (hasError) return '(unavailable)';
    if (dataReady) {
      const resolvedVersion = getVersion(lang, ver);
      return resolvedVersion || match;
    }
    return '...';
  });
  const codeBlockProps = {
    language,
    ...filename && ({
      filename
    }),
    ...icon && ({
      icon
    }),
    ...lines !== undefined && ({
      lines
    }),
    ...wrap !== undefined && ({
      wrap
    }),
    ...expandable !== undefined && ({
      expandable
    }),
    ...highlight && ({
      highlight
    }),
    ...focus && ({
      focus
    })
  };
  return <CodeBlock {...codeBlockProps}>{code}</CodeBlock>;
};

[Qdrant](https://github.com/qdrant/qdrant) is a vector similarity search engine and vector database. It provides a production-ready service with a convenient API to store, search, and manage points—vectors with an additional payload Qdrant is tailored to extended filtering support. It makes it useful for all sorts of neural-network or semantic-based matching, faceted search, and other applications.

While Upsun does not provide an official service image for Qdrant, the database can be configured as a standalone application in a multi-applications project. This still gives you full control over the Qdrant configuration and allows for persistent storage across deployments.

## Configuration

### 1. Configure the Qdrant application

Create a qdrant application in your `.upsun/config.yaml`:

<DynamicCodeBlock language="yaml">
  {`
      applications:
        qdrant:
          type: "composable:{{version:composable:latest}}"
          container_profile: HIGH_MEMORY

          stack:
            - "qdrant"

          source:
            root: "qdrant"

          web:
            commands:
              start: "qdrant --config-path /app/config.yaml"

          mounts:
            "storage":
              source: "storage"
              source_path: "storage"
            "snapshots":
              source: "storage"
              source_path: "snapshots"`
  }
</DynamicCodeBlock>

The example above is using a `HIGH_MEMORY` container. You can refer to the [Container profiles documentation](/docs/manage-resources/adjust-resources#advanced-container-profiles) for more information.

<Info>
  <h4>Available versions</h4>
  In order to run Qdrant as a standalone application without building it from source, the configuration relies on the [Composable image](/docs/configure-apps/app-reference/composable-image). Each [Nix](https://nix.dev/reference/glossary#term-Nix) release (channel) comes with specific package versions. You can check which Qdrant version is available on a specific release on the [Nix packages search](https://search.nixos.org/packages?show=qdrant\&query=qdrant).

  **This is especially important as Qdrant client librairies must use the same minor version `(x.y.*)` as the server.**
</Info>

### 2. Add the required Qdrant configuration

Qdrant relies on its own `config.yaml` configuration file in order to start instead of command-line arguments.

Add a new file named `qdrant/config.yaml` in your project. Note that the `qdrant/` directory matches the `root: "qdrant"` configuration in your `.upsun/config.yaml`.

You can find the [default configuration](https://github.com/qdrant/qdrant/blob/master/config/config.yaml) adapted for Upsun below:

```yaml qdrant/config.yaml theme={null}
log_level: ERROR

storage:
  # Where to store all the data. This is using the mount we declared earlier.
  storage_path: ./storage

  # Where to store snapshots.  This is using the mount we declared earlier.
  snapshots_path: ./snapshots

  snapshots_config:
    snapshots_storage: local

  temp_path: null
  on_disk_payload: true

  update_concurrency: null

  wal:
    wal_capacity_mb: 32
    wal_segments_ahead: 0

  node_type: "Normal"

  performance:
    max_search_threads: 0
    optimizer_cpu_budget: 0
    update_rate_limit: null

  optimizers:
    deleted_threshold: 0.2
    vacuum_min_vector_number: 1000
    default_segment_number: 0
    max_segment_size_kb: null
    indexing_threshold_kb: 10000
    flush_interval_sec: 5
    max_optimization_threads: null

  hnsw_index:
    m: 16
    ef_construct: 100
    full_scan_threshold_kb: 10000
    max_indexing_threads: 0
    on_disk: false
    payload_m: null

  shard_transfer_method: null

  collection:
    replication_factor: 1
    write_consistency_factor: 1
    vectors:
      on_disk: null
    quantization: null
    strict_mode:
      enabled: false
      max_query_limit: null
      max_timeout: null
      unindexed_filtering_retrieve: null
      unindexed_filtering_update: null
      search_max_hnsw_ef: null
      search_allow_exact: null
      search_max_oversampling: null

  max_collections: null

service:
  max_request_size_mb: 32
  max_workers: 0

  # Host to bind the service on
  host: 0.0.0.0

  # HTTP(S) port to bind the service on. We are using Upsun's default 8888. It is unfortunately not possible to use an environment variable here.
  http_port: 8888

  grpc_port: null

  enable_cors: true
  enable_tls: false
  verify_https_client_certificate: false

  # Set an api-key.
  # api_key: your_secret_api_key_here

  # Set an api-key for read-only operations.
  # read_only_api_key: your_secret_read_only_api_key_here

cluster:
  # Use `enabled: true` to run Qdrant in distributed deployment mode
  enabled: false

telemetry_disabled: true
```

The default configuration with original comments can be found on the [Qdrant GitHub repository](https://github.com/qdrant/qdrant/blob/master/config/config.yaml).

### 3. Connect from your application

To connect to Qdrant from another application in your project, add a relationship in that application configuration block:

```yaml .upsun/config.yaml theme={null}
applications:
  myapp:
    source:
      root: "myapp"
    type: "python:3.12"
    relationships:
      qdrant: "qdrant:http"
```

As the example uses `qdrant` as the relationship name, your application will have access to `QDRANT` environments variables.

### 4. Use the relationship in your application

Connect to Qdrant using the relationship configuration:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import os
    from qdrant_client import QdrantClient

    def get_qdrant_client():
        """Create Qdrant client based on environment variables."""
        # Check for remote Qdrant configuration
        qdrant_host = os.getenv("QDRANT_HOST")
        qdrant_port = os.getenv("QDRANT_PORT", "6333")
        qdrant_api_key = os.getenv("QDRANT_API_KEY")

        if qdrant_host:
            # Remote Qdrant instance
            return QdrantClient(
                host=qdrant_host,
                port=int(qdrant_port),
                api_key=qdrant_api_key
            )
        else:
            # Local Qdrant instance (in-memory for testing)
            return QdrantClient(":memory:")
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    import { QdrantClient } from '@qdrant/js-client-rest';

    function getQdrantClient(): QdrantClient {
      const qdrantHost = process.env.QDRANT_HOST;
      const qdrantPort = parseInt(process.env.QDRANT_PORT || '6333');
      const qdrantApiKey = process.env.QDRANT_API_KEY;

      if (qdrantHost) {
        return new QdrantClient({
          url: `http://${qdrantHost}:${qdrantPort}`,
          apiKey: qdrantApiKey
        });
      } else {
        // For local development, connect to localhost
        return new QdrantClient({
          url: 'http://localhost:6333'
        });
      }
    }
    ```
  </Tab>
</Tabs>

**While the examples above are based on Python and Node.js applications, the same concept can be applied to any other runtime.**

## Persistent storage

The configuration includes persistent storage through mounts:

* `storage`: Stores the main Qdrant database files
* `snapshots`: Stores Qdrant snapshots

These mounts ensure that your vector data persists between deployments and application restarts.

## Access Qdrant

Qdrant runs as an internal application without external HTTP access. Other applications in your project connect to it using the `qdrant.internal` hostname through relationships.

For development and debugging, you can use port forwarding to access your Qdrant instance locally:

```bash theme={null}
upsun tunnel:open
```

This creates a secure tunnel to your Qdrant application, allowing you to connect local tools and clients during development.

## Exposing Qdrant on the public internet

If you are willing to make the Qdrant database publicly accessible, add a new route to the application in the `.upsun/config.yaml` file:

```yaml .upsun/config.yaml theme={null}
routes:
  "https://qdrant.{default}/":
    type: upstream
    upstream: "qdrant:http"
```

<Warning>
  <h4>Exposing Qdrant</h4>
  Be mindful that exposing Qdrant publicly can be sensitive from a security standpoint.
</Warning>

## Exporting Data

Qdrant stores vector collections on disk inside the service container.
The recommended export method is the [Qdrant Snapshot API](https://qdrant.tech/documentation/concepts/snapshots/).

1. Open an SSH tunnel to your Qdrant service:

```bash Terminal theme={null}
upsun tunnel:single --relationship <RELATIONSHIP_NAME>
```

By default the REST API is available on port `6333`.

2. Create a snapshot for a specific collection:

```bash Terminal theme={null}
curl -X POST "http://127.0.0.1:6333/collections/<COLLECTION_NAME>/snapshots"
```

The response includes the snapshot name (e.g. `<COLLECTION_NAME>-<TIMESTAMP>.snapshot`).

3. Download the snapshot:

```bash Terminal theme={null}
curl -o <COLLECTION_NAME>.snapshot \
  "http://127.0.0.1:6333/collections/<COLLECTION_NAME>/snapshots/<SNAPSHOT_NAME>"
```

4. To list all collections and snapshot all of them:

```bash Terminal theme={null}
# List collections
curl http://127.0.0.1:6333/collections

# Loop and snapshot each one
for COLLECTION in $(curl -s http://127.0.0.1:6333/collections | jq -r '.result.collections[].name'); do
  curl -X POST "http://127.0.0.1:6333/collections/${COLLECTION}/snapshots"
done
```

The `.snapshot` files can be restored into any Qdrant instance using the
[snapshot recovery endpoint](https://qdrant.tech/documentation/concepts/snapshots/#recover-via-api).

## Other resources

* [Qdrant GitHub)](https://github.com/qdrant/qdrant)
* [Qdrant official website)](https://qdrant.tech/)
