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

# Add services

> See how to add services such as databases, cache, and search engines and configure them to suit your needs.

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 MetaServiceTable = () => {
  const [services, setServices] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const STORAGE_KEY = 'upsun_versions_internal_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = '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(() => {
    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
        }),
        internal: 'true'
      };
      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) {
        setServices([]);
        setLoading(false);
        return;
      }
      const serviceList = [];
      for (const [serviceType, imageData] of Object.entries(data)) {
        if (imageData.service && imageData.versions && Object.keys(imageData.versions).length > 0) {
          const highest = findHighestVersion(imageData.versions);
          serviceList.push({
            type: serviceType,
            name: imageData.name || serviceType,
            version: highest || 'N/A',
            url: imageData.docs.url
          });
        }
      }
      serviceList.sort((a, b) => a.name.localeCompare(b.name));
      setServices(serviceList);
      setLoading(false);
    }).catch(error_ => {
      console.error('MetaServiceTable error:', error_);
      setError(error_.message);
      setLoading(false);
    });
  }, []);
  if (loading) return <p>Loading services...</p>;
  if (error) return <p>Error: {error}</p>;
  if (!services || services.length === 0) return <p>No services found.</p>;
  return <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Type</th>
          <th>Latest Version</th>
        </tr>
      </thead>
      <tbody>
        {services.map(service => <tr key={service.type}>
            <td><a href={service.url} target="_self">{service.name}</a></td>
            <td>{service.type}</td>
            <td>{service.version}</td>
          </tr>)}
      </tbody>
    </table>;
};

Upsun includes many services, so you don't have to subscribe to external cache or search engine services.
Because the services are included in your project, you can manage them through Git
and they're backed up together with the rest of your project.

Your project defines the services configuration from a top-level key called `services`, which is placed in a unified configuration file like `.upsun/config.yaml`.

If you don't need any services (such as for a static website), you don't need to include this configuration. Read on to see how to add services.

## Add a service

Adding a service is a two-step process.

### 1. Configure the service

All service configuration happens in the `.upsun/config.yaml` file in your Git repository.

Configure your service in the following pattern:

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

An example service configuration for two databases might look like this:

<DynamicCodeBlock language="yaml">
  {`
      services:
        # The name of the service container. Must be unique within a project.
        mariadb:
          type: mariadb:{{version:mariadb:latest}}
        # The name of the service container. Must be unique within a project.
        postgresql:
          type: postgresql:{{version:postgresql:latest}}`
  }
</DynamicCodeBlock>

This YAML file contains a dictionary defining all of the services you want to use.
The top-level key `services` defines an object of all of the services to be provisioned for the project.
Below that, come custom service names (`<SERVICE_NAME>`; in the example, `mariadb` and `postgresql`), which you use to identify the service in step 2.

You can give it any name you want with lowercase alphanumeric characters, hyphens, and underscores.

<Note>
  Changing the service name is interpreted as creating an entirely new service.
  This **removes all data in that service**.
  Always back up your data before changing existing services in your `.upsun/config.yaml` file.
</Note>

#### Service options

The following table presents the keys you can define for each service:

| Name            | Type       | Required          | Description                                                                                                                                                                                                                                                      |
| --------------- | ---------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`          | `string`   | Yes               | One of the [available services](#available-services) in the format `type:version`.                                                                                                                                                                               |
| `configuration` | dictionary | For some services | Some services have additional specific configuration options that can be defined here, such as specific endpoints. See the given service page for more details.                                                                                                  |
| `relationships` | dictionary | For some services | Some services require a relationship to your app. The content of the dictionary has the same type as the `relationships` dictionary for [app configuration](/docs/configure-apps/image-properties/relationships). The `endpoint_name` for apps is always `http`. |

#### Resources (CPU, RAM, disk)

Upsun allows you to configure resources (CPU, RAM, and disk) per environment for each of your services.
For more information, see how to [manage resources](/docs/manage-resources).

You configure the disk size in [MB](/docs/glossary#mb).
Your actual available disk space is slightly smaller with some space used for formatting and the filesystem journal.
When checking available space, note whether it's reported in MB or MiB.

You can decrease the size of an existing disk for a service.
If you do so, be aware that:

* You need to [create new backups](/docs/environments/backup) that the downsized disk can accommodate.
  Backups from before the downsize cannot be restored unless you increase the disk size again.
* The downsize fails if there's more data on the disk than the desired size.

### 2. Define the relationship

To define the relationship, 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>:
                  # Other options...

                  # Relationships enable an app container's access to a 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, so long as it's unique between all defined services
    and matches in both the application and services configuration.

    The example above leverages [default endpoint](/docs/configure-apps/image-properties/relationships) configuration for relationships.
    That is, it uses default endpoints behind the scenes, providing a [relationship](/docs/configure-apps/image-properties/relationships)
    (the network address a service is accessible from) that is identical to the *name* of that service.

    Depending on your needs, instead of default endpoint configuration,
    you can use [explicit endpoint configuration](/docs/configure-apps/image-properties/relationships).
  </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>:
                  # Other options...

                  # Relationships enable an app container's access to a 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: <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 the `services` section.
    * `<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).
  </Tab>
</Tabs>

An example relationship to connect to the databases given in the [example in step 1](#1-configure-the-service):

<Tabs>
  <Tab title="Using default endpoints">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # 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.
                <APP_NAME>:
                  relationships:
                    mariadb:
                    postgresql:
              services:
                mariadb:
                  type: mariadb:{{version:mariadb:latest}}
                postgresql:
                  type: postgresql:{{version:postgresql:latest}}`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Using explicit endpoints">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # 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.

                <APP_NAME>:
                  relationships:
                    database:
                      service: mariadb
                      endpoint: mysql
                    postgresql:
                      service: postgresql
                      endpoint: postgresql
              services:
                mariadb:
                  type: mariadb:{{version:mariadb:latest}}
                postgresql:
                  type: postgresql:{{version:postgresql:latest}}`
          }
    </DynamicCodeBlock>

    As with the service name, you can give the relationship any name you want
    with lowercase alphanumeric characters, hyphens, and underscores.
    It helps if the service name and relationship name are different, but it isn't required.
  </Tab>
</Tabs>

Each service offers one or more endpoints for connections, depending on the service.
An endpoint is a named set of credentials to give access to other apps and services in your project.
If you don't specify one in the [service configuration](#service-options), a default endpoint is created.
The default endpoint varies by service, generally being its type (such as `mysql` or `solr`).

## Available services

The following table presents the available service types and their versions.
Add them to the `type` key of the [service configuration](#1-configure-the-service) in the format `type:version`.

<MetaServiceTable />

### Service versions

These services generally follow [semantic versioning conventions](https://semver.org/).
You can select the major version, but the latest compatible minor is applied automatically and can’t be overridden.
Patch versions are applied periodically for bug fixes and the like.
When you deploy your app, you always get the latest available patches.

## Service timezones

All services have their system timezone set to UTC by default.
For some services, you can change the timezone for the running service
(this doesn't affect the container itself and so logs are still in UTC).

* [MySQL](/docs/add-services/mysql#service-timezone)
* [PostgreSQL](/docs/add-services/postgresql#service-timezone)

## Connect to a service

For security reasons, you can't access services directly through HTTP.
You can connect through your app or by opening an SSH tunnel to access the service directly.

<Tabs>
  <Tab title="In an app">
    Once a service is running and exposed via a relationship,
    its credentials (such as the host, username, and password) are automatically available as [service environment variables](/docs/development/variables#service-environment-variables),
    in the `$<RELATIONSHIP-NAME>_<SERVICE-PROPERTY>` format.
    The available information is documented on each service's page, along with sample code for how to connect to it from your app.

    The service environment variable names are fixed, but the values may change if you change the relationship name to the service.
    So **use the environment variable** rather than hard coding the values.
  </Tab>

  <Tab title="Through an SSH tunnel">
    Connecting to a service using an SSH tunnel is a two-step process.

    ### 1. Obtain service credentials

    To get the credentials for a given service, run the following command:

    ```bash theme={null}
    upsun relationships
    ```

    You get output like the following:

    <DynamicCodeBlock language="yaml">
      {`
              mariadb:
                -
                  username: user
                  scheme: mysql
                  service: mariadb
                  fragment: null
                  ip: 198.51.100.37
                  hostname: abcdefghijklm1234567890123.mariadb.service._.eu.platformsh.site
                  public: false
                  cluster: abcdefgh1234567-main-abcd123
                  host: mariadb.internal
                  rel: mysql
                  query:
                      is_master: true
                  path: main
                  password: ''
                  type: 'mariadb:{{version:mariadb:latest}}'
                  port: 3306
                  host_mapped: false
                  url: 'mysql://user:@mariadb.internal:3306/main'`
          }
    </DynamicCodeBlock>

    With this example, you can connect to the `mariadb` relationship
    with the user `user`, an empty password, and the database name `main` (from the `path`).
    The `url` property shows a full database connection that can be used from your app.

    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.

    ### 2. Open an SSH tunnel

    Open a single [SSH tunnel](/docs/development/ssh#connect-to-services) by running the following CLI command:

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

    By default, this opens a tunnel at `127.0.0.1:30000`.
    You can specify the port for the connection using the `--port` flag.

    You can then connect to this service in a separate terminal or locally running app.
    With the example above, you connect to a URL like the following:
    `mysql://user:@127.0.0.1:30000/main`
  </Tab>
</Tabs>

## Upgrading services

Upsun provides a large number of [managed service versions](#available-services).
As new versions are made available, you will inevitably upgrade infrastructure to a more recent (or latest version).

When you do so, we would recommend:

1. **Use preview environments**. Leverage preview (non-production environments) to perform the upgrade, then merge the upgrade into production (promotion). This will give you an opportunity to test inherited production data in a safe, isolated environment first.
2. **Upgrade progressively**. For one reason or another, you may be more than a single version behind the upgrade you are trying to perform. To avoid data loss issues caused by large differences in versions, [upgrade one version at a time](https://www.rabbitmq.com/upgrade.html#rabbitmq-version-upgradability).
