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

# ClickHouse

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

export const VariableBlock = ({name}) => {
  return <var spellCheck={false} title={`Replace '${name}' with your own data`}>{name}</var>;
};

export const MetaImageVersionList = ({language, status}) => {
  const [versions, setVersions] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const STORAGE_KEY = 'upsun_versions_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = 'https://meta.upsun.com/images';
  useEffect(() => {
    if (!language) {
      setLoading(false);
      return;
    }
    setLoading(true);
    setError(null);
    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 (error_) {
          console.error('Failed to load from cache:', error_);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        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 (error_) {
            console.error('Failed to refresh cache metadata:', error_);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (error_) {
          console.error('Failed to cache data:', error_);
        }
      }
      return data;
    };
    fetchData().then(data => {
      if (!data || !data[language]) {
        setVersions([]);
        setLoading(false);
        return;
      }
      const imageData = data[language];
      if (!imageData.versions) {
        setVersions([]);
        setLoading(false);
        return;
      }
      let versionList = Object.entries(imageData.versions).map(([name, v]) => ({
        name,
        status: v.upsun?.status || v.status
      })).sort((a, b) => {
        const aParts = a.name.split('.').map(Number);
        const bParts = b.name.split('.').map(Number);
        const max = Math.max(aParts.length, bParts.length);
        for (let i = 0; i < max; i++) {
          const av = aParts[i] || 0;
          const bv = bParts[i] || 0;
          if (av !== bv) return bv - av;
        }
        return 0;
      });
      if (status) {
        versionList = versionList.filter(v => v.status === status);
      }
      setVersions(versionList);
      setLoading(false);
    }).catch(error_ => {
      console.error('MetaImageVersionList error:', error_);
      setError(error_.message);
      setLoading(false);
    });
  }, [language, status]);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  if (!versions || versions.length === 0) {
    if (status === 'incoming') return null;
    return <p>No versions available! Contact support.</p>;
  }
  let incomingBlock = null;
  if (status === 'incoming' && versions.length > 0) {
    incomingBlock = `These versions are not yet available but are expected to be released soon.`;
  }
  return incomingBlock ? <Note>
      <p>{incomingBlock}</p>
      <ul>
        {versions.map(version => <li className="image-version" key={version.name}>
            {version.name} {version.status === 'beta' && <span className="badge">Beta</span>}
          </li>)}
      </ul>
    </Note> : <ul>
      {versions.map(version => <li className="image-version" key={version.name}>
          {version.name} {version.status === 'beta' && <span className="badge">Beta</span>}
        </li>)}
    </ul>;
};

export const MetaImageVersion = ({language, version}) => {
  const [selectedVersion, setSelectedVersion] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const isComposable = language === 'composable';
  const STORAGE_KEY = isComposable ? 'upsun_composable_cache' : 'upsun_versions_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = isComposable ? 'https://meta.upsun.com/composable' : 'https://meta.upsun.com/images';
  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;
  };
  useEffect(() => {
    if (!language) {
      setLoading(false);
      return;
    }
    setLoading(true);
    setError(null);
    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 (error_) {
          console.error('Failed to load from cache:', error_);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        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 (error_) {
            console.error('Failed to refresh cache metadata:', error_);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (error_) {
          console.error('Failed to cache data:', error_);
        }
      }
      return data;
    };
    fetchData().then(data => {
      if (!data) {
        setSelectedVersion(null);
        setLoading(false);
        return;
      }
      const imageData = isComposable ? data : data[language];
      if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
        setSelectedVersion(null);
        setLoading(false);
        return;
      }
      let versionName = null;
      if (version && version !== 'latest') {
        versionName = (version in imageData.versions) ? version : null;
      } else {
        versionName = findHighestVersion(imageData.versions);
      }
      setSelectedVersion(versionName);
      setLoading(false);
    }).catch(error_ => {
      console.error('MetaImageVersion error:', error_);
      setError(error_.message);
      setLoading(false);
    });
  }, [language, version]);
  if (loading) return <span>…</span>;
  if (error) return <span title={error}>⚠ unavailable</span>;
  if (!selectedVersion) return <span>No version found</span>;
  return <span>{selectedVersion}</span>;
};

ClickHouse is a high-performance column-oriented, distributed, OLAP (Online Analytical Processing) database.<br />
It allows you to generate real-time analytical data reports using SQL queries.<br />
For more information, see the [ClickHouse documentation](https://ClickHouse.com/docs).

<Note>
  Upsun supports ClickHouse with the following limitations:

  * High availability of service isn't supported.
  * You can only configure single-node ClickHouse clusters.
</Note>

## Supported versions

<MetaImageVersionList language="clickhouse" status="supported" platform="grid" />

Upsun plans on supporting long-term support ClickHouse versions in priority.

<MetaImageVersionList language="clickhouse" status="incoming" platform="grid" />

## Deprecated versions

The following versions are still available in your projects,
but they're at their end of life and are no longer receiving security updates from upstream.

<MetaImageVersionList language="clickhouse" status="deprecated" platform="grid" />

To ensure your project remains stable in the future,
switch to a [supported version](#supported-versions).

## Retired versions

The following versions have been retired and are no longer available.
If your project uses a retired version, you must update to a [supported version](#supported-versions).

<MetaImageVersionList language="clickhouse" status="retired" platform="grid" />

## Relationship reference

For each service [defined via a relationship](#usage-example) to your application,
Upsun automatically generates corresponding environment variables within your application container,
in the `$<RELATIONSHIP-NAME>_<SERVICE-PROPERTY>` format.

Here is example information available through the [service environment variables](/docs/development/variables#service-environment-variables) themselves,
or through the [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables).

<Tabs>
  <Tab title="Service environment variables">
    You can obtain the complete list of available service environment variables in your app container by running `upsun ssh env`.

    Note that the information about the relationship can change when an app is redeployed or restarted or the relationship is changed. So your apps should only rely on the [service environment variables](/docs/development/variables#service-environment-variables) directly rather than hard coding any values.

    <DynamicCodeBlock language="bash">
      {`
              CLICKHOUSE_USERNAME=main
              CLICKHOUSE_FRAGMENT=
              CLICKHOUSE_IP=123.456.78.90
              CLICKHOUSE_CLUSTER=azertyuiop-main-afdwftq
              CLICKHOUSE_HOST=clickhouse.internal
              CLICKHOUSE_PATH=main
              CLICKHOUSE_QUERY={'is_master': true}
              CLICKHOUSE_PASSWORD=ChangeMe
              CLICKHOUSE_PORT=9000
              CLICKHOUSE_HOST_MAPPED=false
              CLICKHOUSE_SERVICE=clickhouse
              CLICKHOUSE_HOSTNAME=azertyuiopqsdfghjklm.clickhouse.service._.eu-1.platformsh.site
              CLICKHOUSE_EPOCH=0
              CLICKHOUSE_REL=clickhouse
              CLICKHOUSE_SCHEME=https/http
              CLICKHOUSE_TYPE=clickhouse:{{version:clickhouse:latest}}
              CLICKHOUSE_PUBLIC=false
            `}
    </DynamicCodeBlock>
  </Tab>

  <Tab title="`PLATFORM_RELATIONSHIPS` environment variable">
    For some advanced use cases, you can use the [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables).
    The structure of the `PLATFORM_RELATIONSHIPS` environment variable can be obtained by running `upsun relationships` in your terminal:

    <DynamicCodeBlock language="json">
      {`
              {
                "username": "main",
                "fragment": null,
                "ip": "123.456.78.90",
                "cluster": "azertyuiop-main-afdwftq",
                "host": "clickhouse.internal",
                "path": "main",
                "query": {
                  "is_master": true
                },
                "password": "ChangeMe",
                "port": 9000,
                "host_mapped": false,
                "service": "clickhouse",
                "hostname": "azertyuiopqsdfghjklm.clickhouse.service._.eu-1.platformsh.site",
                "epoch": 0,
                "rel": "clickhouse",
                "scheme": "https",
                "type": "clickhouse:{{version:clickhouse:latest}}",
                "public": false
              }
            `}
    </DynamicCodeBlock>

    Here is an example of how to gather [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables) information in a [`.environment` file](/docs/development/variables/set-variables#when-to-use-env-files):

    ```bash .environment theme={null}
    # Decode the built-in credentials object variable.
    export RELATIONSHIPS_JSON="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode)"

    # Set environment variables for individual credentials.
    export APP_CLICKHOUSE_HOST=="$(echo "$RELATIONSHIPS_JSON" | jq -r '.clickhouse[0].host')"
    ```
  </Tab>
</Tabs>

## Usage example

### 1. Configure the service

To define the service, use the `clickhouse` type:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      services:
        # The name of the service container. Must be unique within a project.
        <SERVICE_NAME>:
          type: clickhouse:<VERSION>
    `}
</DynamicCodeBlock>

Note that changing the name of the service replaces it with a brand new service and all existing data is lost. Back up your data before changing the service.

### 2. Define the relationship

To define the relationship, use one of the following endpoints.

#### `clickhouse` endpoint

The `clickhouse` endpoint allows you to use the Native Protocol port (also known as ClickHouse TCP protocol).
This protocol is used by ClickHouse apps and processes such as `clickhouse-server`, `clickhouse-client`, and native ClickHouse tools. It is also used for inter-server communication for distributed queries.

Use the following configuration:

<Tabs>
  <Tab title="Using default endpoints">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                <APP_NAME>:
                  # Relationships enable access from this app to a given service.
                  # The example below shows simplified configuration leveraging a default service
                  # (identified from the relationship name) and a default endpoint.
                  # See the Application reference for all options for defining relationships and endpoints.
                  relationships:
                    <SERVICE_NAME>:
            `}
    </DynamicCodeBlock>

    You can define `SERVICE_NAME` as you like.
  </Tab>

  <Tab title="Using explicit endpoints">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                <APP_NAME>:
                  # Relationships enable access from this app to a given service.
                  # The example below shows configuration with an explicitly set service name and endpoint.
                  # See the Application reference for all options for defining relationships and endpoints.
                  relationships:
                    <RELATIONSHIP_NAME>:
                      service: <SERVICE_NAME>
                      endpoint: clickhouse
            `}
    </DynamicCodeBlock>

    You can define `SERVICE_NAME` and `<RELATIONSHIP_NAME>` as you like, but it's best if they're distinct.
    With this definition, the application container (`<APP_NAME>:`) now has access to the service via the corresponding [service environment variables](/docs/development/variables#service-environment-variables).
  </Tab>
</Tabs>

#### `clickhouse-http` endpoint

The `clickhouse-http` endpoint allows you to use the HTTP API Port for HTTP requests.
This protocol is used by [JDBC](https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/), [ODBC](https://learn.microsoft.com/en-us/sql/odbc/microsoft-open-database-connectivity-odbc?view=sql-server-ver16), and web interfaces.

Use the following configuration:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The name of the app container. Must be unique within a project.
        <APP_NAME>:
          # Relationships enable access from this app to a given service.
          # The example below shows configuration with an explicitly set service name and endpoint.
          # See the Application reference for all options for defining relationships and endpoints.
          relationships:
            <RELATIONSHIP_NAME>:
              service: <SERVICE_NAME>
              endpoint: clickhouse-http
      services:
        # The name of the service container. Must be unique within a project.
        <SERVICE_NAME>:
            type: clickhouse:<VERSION>
    `}
</DynamicCodeBlock>

You can define `SERVICE_NAME` and `<RELATIONSHIP_NAME>` as you like, so long as it's unique between all defined services and relationships
and matches in both the application and services configuration.

With this definition, the application container (`<APP_NAME>:`) now has access to the service via the corresponding [service environment variables](/docs/development/variables#service-environment-variables).

### Example configuration

<Tabs>
  <Tab title="With `clickhouse` default endpoint">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  source:
                    root: "/"
                  # Relationships enable access from this app to a given service.
                  # The example below shows simplified configuration leveraging a default service
                  # (identified from the relationship name) and a default endpoint.
                  # See the Application reference for all options for defining relationships and endpoints.
                  relationships:
                    clickhouse:
              services:
                # The name of the service container. Must be unique within a project.
                clickhouse:
                  type: clickhouse:{{version:clickhouse:latest}}`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="With `clickhouse` explicit endpoint">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  source:
                    root: "/"
                  # Relationships enable access from this app to a given service.
                  # The example below shows configuration with an explicitly set service name and endpoint.
                  # See the Application reference for all options for defining relationships and endpoints.
                  relationships:
                    clickhouse:
                      service: clickhouse
                      endpoint: clickhouse
              services:
                # The name of the service container. Must be unique within a project.
                clickhouse:
                  type: clickhouse:{{version:clickhouse:latest}}`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="With `clickhouse-http` endpoint">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  source:
                    root: "/"
                  # Relationships enable access from this app to a given service.
                  # The example below shows configuration with an explicitly set service name and endpoint.
                  # See the Application reference for all options for defining relationships and endpoints.
                  relationships:
                    clickhouse:
                      service: clickhouse
                      endpoint: clickhouse-http
              services:
                # The name of the service container. Must be unique within a project.
                clickhouse:
                  type: clickhouse:{{version:clickhouse:latest}}`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

If you want to change the `clickhouse` endpoint to `clickhouse-http`, you need to use explicit endpoint definition as it defaults to `clickhouse` endpoint when using default endpoint (aka. `<SERVICE_NAME>` as relationship definition).

## Multiple databases

You can configure multiple databases, much like [with PostgreSQL](/docs/add-services/postgresql#multiple-databases).
To do so, you can use a configuration similar to the following:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      # Complete list of all available properties: /docs/configure-apps/app-reference
      applications:
        myapp:
          # Relationships enable access from this app to a given service.
          # The example below shows configuration with explicitly set service names and endpoints.
          # See the Application reference for all options for defining relationships and endpoints.
          relationships:
            clickhouse-admin:
              service: clickhouse
              endpoint: admin
            clickhouse-reporter:
              service: clickhouse
              endpoint: reporter
            clickhouse-importer:
              service: clickhouse
              endpoint: importer
      services:
        clickhouse:
          type: clickhouse:{{version:clickhouse:latest}}
          configuration:
            databases:
              - main
              - legacy
          endpoints:
            admin:
              port: 9000 # binary port
            privileges:
              main: admin
              legacy: admin
          reporter:
            default_database: main
            port: 8123 # http port
            privileges:
              main: ro
          importer:
            default_database: legacy
            port: 9000 # binary port
            privileges:
              legacy: rw`
  }
</DynamicCodeBlock>

## Exporting Data

ClickHouse data can be exported using the native `clickhouse-client` tool over an SSH tunnel.

1. Open an SSH tunnel to your ClickHouse service:

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

By default this opens the tunnel on `127.0.0.1:30000`.

2. Export a table or the result of a query to a file:

```bash Terminal theme={null}
clickhouse-client \
  --host 127.0.0.1 \
  --port 9000 \
  --user <CLICKHOUSE_USERNAME> \
  --password <CLICKHOUSE_PASSWORD> \
  --database <CLICKHOUSE_PATH> \
  --query "SELECT * FROM <TABLE_NAME>" \
  --format CSVWithNames > table_export.csv
```

You can replace `CSVWithNames` with any [ClickHouse output format](https://clickhouse.com/docs/en/interfaces/formats)
such as `Parquet`, `JSONEachRow`, or `Native` depending on your needs.

3. To export the full schema of your database:

```bash Terminal theme={null}
clickhouse-client \
  --host 127.0.0.1 \
  --port 9000 \
  --user <CLICKHOUSE_USERNAME> \
  --password <CLICKHOUSE_PASSWORD> \
  --query "SHOW CREATE TABLE <TABLE_NAME>"
```

The credentials (`<CLICKHOUSE_USERNAME>`, `<CLICKHOUSE_PASSWORD>`, `<CLICKHOUSE_PATH>`) are available
in the service environment variables. Run `upsun ssh -- env | grep CLICKHOUSE` to retrieve them.
