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

# Relationships

> A dictionary of relationships that defines the connections to other services and apps.

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

Optional in [single-runtime](/docs/configure-apps/app-reference/single-runtime-image#primary-application-properties) and [composable](/docs/configure-apps/app-reference/composable-image#primary-application-properties) images.

To allow containers in your project to communicate with one another,
you need to define relationships between them.
You can define a relationship between an app and a service, or [between two apps](/docs/configure-apps/multi-app/relationships).

The quickest way to define a relationship between your app and a service
is to use the service's default endpoint.<br />
However, some services allow you to define multiple databases, cores, and/or permissions.
In these cases, you can't rely on default endpoints.
Instead, you can explicitly define multiple endpoints when setting up your relationships.

<Note>
  App containers don't have a default endpoint like services.
  To connect your app to another app in your project,
  you need to explicitly define the `http` endpoint as the endpoint to connect both apps.<br />
  For more information, see how to [define relationships between your apps](/docs/configure-apps/multi-app/relationships).
</Note>

<Info>
  <h4>Availability</h4>
  New syntax (default and explicit endpoints) described below is supported by most, but not all, image types
  (`Relationship 'SERVICE_NAME' of application 'app' ... targets a service without a valid default endpoint configuration.`).
  This syntax is currently being rolled out for all images.
  If you encounter this error, use the "legacy" Upsun configuration noted at the bottom of this section.
</Info>

To define a relationship between your app and a service:

<Tabs>
  <Tab title="Using default endpoints">
    Use the following configuration:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    <SERVICE_NAME>:`
          }
    </DynamicCodeBlock>

    The `SERVICE_NAME` is the name of the service as defined in its [configuration](/docs/add-services).
    It is used as the relationship name, and associated with a `null` value.
    This instructs Upsun to use the service's default endpoint to connect your app to the service.

    For example, if you define the following configuration:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    mariadb:`
          }
    </DynamicCodeBlock>

    Upsun looks for a service named `mariadb` in your `.upsun/config.yaml` file,
    and connects your app to it through the service's default endpoint.

    For reference, the equivalent configuration using explicit endpoints would be the following:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    mariadb:
                      service: mariadb
                      endpoint: mysql`
          }
    </DynamicCodeBlock>

    You can define any number of relationships in this way:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    mariadb:
                    redis:
                    elasticsearch:`
          }
    </DynamicCodeBlock>

    <Info>
      <h4>Tip</h4>
      An even quicker way to define many relationships is to use the following single-line configuration:

      <DynamicCodeBlock language="yaml">
        {`
                  applications:
                    <APP_NAME>:
                      # ...
                      relationships: 
                        <SERVICE_NAME_A>
                        <SERVICE_NAME_B>
                        <SERVICE_NAME_C>

                  services:
                    <SERVICE_NAME_A>:
                      type: mariadb:{{version:mariadb:latest}}
                    <SERVICE_NAME_B>:
                      type: redis:{{version:redis:latest}}
                    <SERVICE_NAME_C>:
                      type: elasticsearch:{{version:elasticsearch:latest}}`
              }
      </DynamicCodeBlock>
    </Info>
  </Tab>

  <Tab title="Using explicit endpoints">
    Use the following configuration:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    <RELATIONSHIP_NAME>:
                      service: <SERVICE_NAME>
                      endpoint: <ENDPOINT_NAME>`
          }
    </DynamicCodeBlock>

    * `RELATIONSHIP_NAME` is the name you want to give to the relationship.
    * `SERVICE_NAME` is the name of the service as defined in its [configuration](/docs/add-services).
    * `ENDPOINT_NAME` is the endpoint your app will use to connect to the service (refer to the service reference to know which value to use).

    For example, to define a relationship named `database` that connects your app to a service called `mariadb` through the `db1` endpoint,
    use the following configuration:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    database: # The name of the relationship.
                      service: mariadb
                      endpoint: db1`
          }
    </DynamicCodeBlock>

    For more information on how to handle multiple databases, multiple cores,
    and/or different permissions with services that support such features,
    see each service's dedicated page:

    * [MariaDB/MySQL](/docs/add-services/mysql#multiple-databases) (multiple databases and permissions)
    * [PostgreSQL](/docs/add-services/postgresql#multiple-databases) (multiple databases and permissions)
    * [Redis](/docs/add-services/redis#multiple-databases) (multiple databases)
    * [Solr](/docs/add-services/solr#solr-6-and-later) (multiple cores)
    * [Vault KMS](/docs/add-services/vault#multiple-endpoints-example) (multiple permissions)

    You can add as many relationships as you want to your app configuration,
    using both default and explicit endpoints according to your needs:

    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                <APP_NAME>:
                  # ...
                  relationships:
                    database1:
                      service: mariadb
                      endpoint: admin
                    database2:
                      service: mariadb
                      endpoint: legacy
                    cache:
                      service: redis
                    search:
                      service: elasticsearch`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

<Info>
  <h4>Legacy</h4>
  The following legacy syntax for specifying relationships is still supported by Upsun:

  <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
    {`
          applications:
            <APP_NAME>:
              # ...
              relationships:
                <RELATIONSHIP_NAME>: "<SERVICE_NAME>:<ENDPOINT_NAME>"

          services:
            SERVICE_NAME_A:
              type: mariadb:{{version:mariadb:latest}}`
      }
  </DynamicCodeBlock>

  For example:

  <DynamicCodeBlock language="yaml">
    {`
          applications:
            <APP_NAME>:
              # ...
              relationships:
                database: "db:mysql"

          services:
            db:
              type: mariadb:{{version:mariadb:latest}}`
      }
  </DynamicCodeBlock>

  Feel free to use this until the default and explicit endpoint syntax is supported on all images.
</Info>
