> ## 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.

# Chroma

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>;
};

[Chroma](https://www.trychroma.com/) is an open-source vector database designed for AI applications that need to store, query, and manage embeddings efficiently.

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

Chroma excels in several use cases:

* **Semantic Search**: Find documents based on meaning rather than exact keyword matches
* **Retrieval Augmented Generation (RAG)**: Enhance LLMs with relevant context from your knowledge base
* **Recommendation Systems**: Build similarity-based recommendation engines
* **Content Classification**: Automatically categorize documents based on their semantic content
* **Duplicate Detection**: Identify similar or duplicate content across large document collections

## Configuration

### 1. Configure the Chroma application

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

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        chroma:
          type: "python:{{version:python:latest}}"
          container_profile: HIGH_MEMORY
          source:
            root: "chroma"
          dependencies:
            python3:
              uv: "*"

          hooks:
            build: |
              uv init
              uv add chromadb

          web:
            commands:
              start: "uv run --no-sync chroma run --host 0.0.0.0 --port $PORT --path /app/.db"

          mounts:
            ".db":
              source: "storage"
              source_path: "db"
            ".chroma":
              source: "storage"
              source_path: "chroma"

          variables:
            env:
              uv_CACHE_DIR: "/tmp/uv-cache"
              PYTHONPATH: "."
    `}
</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.

### 2. Connect from your application

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

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        myapp:
          source:
            root: "myapp"
          type: "python:{{version:python:latest}}"
          relationships:
            chroma: "chroma:http"
    `}
</DynamicCodeBlock>

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

### 3. Use the relationship in your application

Connect to Chroma using the relationship configuration:

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

    def get_chroma_client():
        """Create ChromaDB client based on environment variables."""
        # Check for remote ChromaDB configuration
        chroma_host = os.getenv("CHROMA_HOST")
        chroma_port = os.getenv("CHROMA_PORT", "8000")
        chroma_ssl = os.getenv("CHROMA_SSL", "false").lower() == "true"
        chroma_headers = {}

        # Optional authentication
        if os.getenv("CHROMA_AUTH_TOKEN"):
            chroma_headers["Authorization"] = f"Bearer {os.getenv('CHROMA_AUTH_TOKEN')}"

        if chroma_host:
            # Remote ChromaDB instance
            return chromadb.HttpClient(
                host=chroma_host,
                port=int(chroma_port),
                ssl=chroma_ssl,
                headers=chroma_headers
            )
        else:
            # Local ChromaDB instance
            return chromadb.Client()
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    import { ChromaClient, OpenAIEmbeddingFunction } from 'chromadb';

    function getChromaClient(): ChromaClient {
      const chromaHost = process.env.CHROMA_HOST;
      const chromaPort = parseInt(process.env.CHROMA_PORT || '8000');
      const chromaSsl = process.env.CHROMA_SSL?.toLowerCase() === 'true';

      if (chromaHost) {
        const auth = process.env.CHROMA_AUTH_TOKEN
          ? { provider: 'token', credentials: process.env.CHROMA_AUTH_TOKEN }
          : undefined;

        return new ChromaClient({
          path: `http${chromaSsl ? 's' : ''}://${chromaHost}:${chromaPort}`,
          auth
        });
      } else {
        return new ChromaClient();
      }
    }
    ```
  </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:

* `.db`: Stores the main Chroma database files
* `.chroma`: Stores additional Chroma metadata

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

## Access Chroma

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

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

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

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

## Exposing Chroma on the public internet

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

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

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

## Exporting Data

Chroma stores its vector database on disk as SQLite files within the service container's mount directory.
The data is directly accessible via the mount path and can be downloaded using the CLI or rsync.

1. Identify the mount path where Chroma stores its data (typically defined in your `.upsun/config.yaml`
   as a `source: service` mount pointing to the Chroma service).

2. Download the data directory using the CLI:

```bash Terminal theme={null}
upsun mount:download --mount <CHROMA_MOUNT_PATH> --target ./chroma-backup
```

3. The downloaded directory contains the SQLite database files (`.db`) used by Chroma.
   These can be restored by placing them back in the mount path of a new Chroma service.

## Other resources

* [DevCenter: Store embeddings in chroma with persistent storage (nodejs and python examples)](https://devcenter.upsun.com/posts/store-embeddings-in-chroma-with-persistent-storage-nodejs-and-python-examples/)
