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

# Deploy Magento on Upsun

> Complete these steps to successfully deploy Magento on Upsun.


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 GuidesRequirements = ({name}) => {
  const isSymfony = name === "Symfony";
  return <>
      <h2>Before you begin</h2>
      <p>You need:</p>
      <ul>
        <li>
          <a href="https://git-scm.com/downloads">Git</a>.{' '}
          Git is the primary tool to manage everything your app needs to run.
          Push commits to deploy changes and control configuration through YAML files.
          These files describe your infrastructure, making it transparent and version-controlled.
        </li>
        <li>
          An Upsun account.{' '}
          If you don't already have one, <a href="https://auth.upsun.com/register">register for a trial account</a>.{' '}
          You can sign up with an email address or an existing GitHub, Bitbucket, or Google account.
          If you choose one of these accounts, you can set a password for your Upsun account later.
        </li>
        <li>
          The {isSymfony ? <a href="https://symfony.com/download">Symfony CLI</a> : <a href="/cli">Upsun CLI</a>}.{' '}
          This lets you interact with your project from the command line.
          You can also do most things through the <a href="/docs/administration/web">Web Console</a>.
        </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>;
};

Before attempting to deploy Magento on Upsun, you **must complete the [Getting started guide](/docs/get-started/here)**.

You can also **check out the [Upsun demo app](https://console.upsun.com/projects/create-project)** and [Resource configuration guide](/docs/manage-resources/adjust-resources). These provide all of the core concepts and common commands you need to easily follow this Magento guide.

<GuidesRequirements name="Drupal" />

<Info>
  <h4>Authentication</h4>
  You will not need Adobe Commerce authentication keys for this process but if you would like to learn how to set them up if you want to adjust the Composer repository to [https://repo.magento.com/](https://repo.magento.com/), visit [Adobe Commerce Authentication](https://experienceleague.adobe.com/en/docs/commerce-on-cloud/user-guide/develop/authentication-keys).
</Info>

We will be using the [Upsun Magento example project](https://github.com/platformsh-templates/magentoCE24/blob/main/README) for this deployment process. The example specifically features:

* PHP <MetaImageVersion language="php" />
* MariaDB <MetaImageVersion language="mariadb" />
* Redis <MetaImageVersion language="redis" />
* OpenSearch <MetaImageVersion language="opensearch" />
* RabbitMQ <MetaImageVersion language="rabbitmq" />
* Automatic TLS certificates
* Composer-based build

The example also features an [Upsun config.yaml](https://github.com/platformsh-templates/magentoCE24/blob/main/.upsun/config.yaml) file. Below is a **shortened example** of the config.yaml file:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
          <APP_NAME>:
              # The runtime the application uses.
              type: php:{{version:php:latest}}
              # Specify additional PHP extensions that should be loaded.
              runtime:
                  extensions:
                      - xsl
                      - sodium
                      - redis
                      - blackfire
              variables:
                  env:
                      NVM_VERSION: master
                      NODE_VERSION: 20
                      MAGENTO_DC_INDEXER__USE_APPLICATION_LOCK: true
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOGINVENTORY_STOCK__SIMPLE: 200
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOG_CATEGORY_PRODUCT: 666
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOGSEARCH_FULLTEXT__PARTIAL_REINDEX: 100
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOGSEARCH_FULLTEXT__MYSQL_GET: 500
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOGSEARCH_FULLTEXT__ELASTIC_SAVE: 500
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOG_PRODUCT_PRICE__SIMPLE: 200
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOG_PRODUCT_PRICE__DEFAULT: 500
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOG_PRODUCT_PRICE__CONFIGURABLE: 666
                      MAGENTO_INDEXER_BATCH_SIZE__CATALOGPERMISSIONS_CATEGORY: 999
                      MAGENTO_INDEXER_BATCH_SIZE__INVENTORY__SIMPLE: 210
                      MAGENTO_INDEXER_BATCH_SIZE__INVENTORY__DEFAULT: 510
                      MAGENTO_INDEXER_BATCH_SIZE__INVENTORY__CONFIGURABLE: 616
                  php:
                      memory_limit: "512M"
                    ...`
      }
</DynamicCodeBlock>

## Create project

Copy and run the following command in command line:

`upsun project:create --init-repo https://github.com/platformsh-templates/magentoCE24/`

You will then have to make the following selections:

* Select an organization to create the project under
* Select a title for your project
* Select a [region to deploy from](/docs/development/regions#regions)
* Select your default branch
* Select whether you would like to set your new project as the remote for any existing repositories that have been detected under your organization

Once you have made your selections, the Upsun bot will activate your project.

When your project is created, you will be provided with the following details to access it:

* `Region`
* `Project ID`
* `Project title`
* `Project URL`
* `Git URL`

## Configure resources

Copy the Project URL into your browser. You should see your newly created project in the Upsun console. For example, if you had named your Magento project `Mage`, you would see something similar to the screenshot below:

<img src="https://mintcdn.com/upsun-c9761871/7cK3KMJBgO7MXm_y/images/guides/magento/mage-console-1.png?fit=max&auto=format&n=7cK3KMJBgO7MXm_y&q=85&s=dfdc8499a71dd8baef1a5a9be68d0a30" alt="Your magento project in the Upsun console" width="1512" height="885" data-path="images/guides/magento/mage-console-1.png" />

You will be prompted to configure your resources. At this stage you can select the CPU, RAM, instances and disk size for your Magento project.

<img src="https://mintcdn.com/upsun-c9761871/7cK3KMJBgO7MXm_y/images/guides/magento/configure-mage-resources.png?fit=max&auto=format&n=7cK3KMJBgO7MXm_y&q=85&s=96a4291f3a76da6bc6560afc0b61215f" alt="Configure the resources for your Magento project in the Upsun console" width="3024" height="1646" data-path="images/guides/magento/configure-mage-resources.png" />

### Recommended configurations

You will see that the `container_profile` for the app container is using the `BALANCED` profile by default. The standard CPU & RAM recommendation for your app container is a minimum of `0.5 CPU, 1088MB RAM`. Please see the table below for all **recommended minimum settings for your app container**:

| CPU & RAM             | Instances  | Disk/storage |
| --------------------- | ---------- | ------------ |
| `0.5 CPU, 1088MB RAM` | No minimum | `256MB`      |

All other services will be using their [default container profiles](/docs/manage-resources/adjust-resources#advanced-container-profiles) and therefore can be set to `0.1 CPU`, so the above values only apply as recommended minimums for your app container.

Once your project is deployed, you may need to [adjust your resources](/docs/manage-resources/adjust-resources) and [adjust the container profiles](/docs/manage-resources/adjust-resources#adjust-a-container-profile) of your other services.

<Info>
  <h4>Note</h4>
  Please note that the deployment after configuring resources may take up to 25 minutes.
</Info>

## View your log

Now that your resources have been configured, you can view a log of how Upsun creates your project. In the recents section, click the three dots to the right of the activity about your `updated resource allocation on Main`.

<img src="https://mintcdn.com/upsun-c9761871/7cK3KMJBgO7MXm_y/images/guides/magento/log-console-1.png?fit=max&auto=format&n=7cK3KMJBgO7MXm_y&q=85&s=a46ce93a766244d7bcc8e4f0109d74cb" alt="Navigate to the log to see your Magento project being created" width="978" height="182" data-path="images/guides/magento/log-console-1.png" />

Below is a **shortened example** of what your log would look like:

<DynamicCodeBlock language="log">
  {`
       Configuring resources
         Setting 'app' resources to 0.5 CPU, 1088MB RAM.
         Setting 'app' disk to 256MB.
         Setting 'db' resources to 0.1 CPU, 448MB RAM.
         Setting 'db' disk to 256MB.
         Setting 'cache' resources to 0.1 CPU, 352MB RAM.
         Setting 'session' resources to 0.1 CPU, 352MB RAM.
         Setting 'session' disk to 256MB.
         Setting 'indexer' resources to 0.1 CPU, 448MB RAM.
         Setting 'indexer' disk to 256MB.
         Setting 'queue' resources to 0.1 CPU, 448MB RAM.
         Setting 'queue' disk to 256MB.

       Building application 'app' (runtime type: php:{{version:php:latest}}, tree: 392d8f3)
         Generating runtime configuration.

         Installing build dependencies...

          ...

         Environment configuration
           app (type: php:{{version:php:latest}}, cpu: 0.1, memory: 64, disk: 1024)
           db (type: mariadb:{{version:mariadb:latest}}, cpu: 0.1, memory: 448, disk: 256)
           cache (type: redis:{{version:redis:latest}}, cpu: 0.1, memory: 352)
           session (type: redis-persistent:{{version:redis:latest}}, cpu: 0.1, memory: 352, disk: 256)
           indexer (type: opensearch:{{version:opensearch:latest}}, cpu: 0.1, memory: 448, disk: 256)
           queue (type: rabbitmq:{{version:rabbitmq:latest}}, cpu: 0.1, memory: 448, disk: 256)

         Environment routes
           http://main-bvxea6i-g7baduaayq63y.eu-5.platformsh.site/ redirects to https://main-bvxea6i-g7baduaayq63y.eu-5.platformsh.site/
           http://main-bvxea6i-g7baduaayq63y.eu-5.platformsh.site/static/ redirects to https://main-bvxea6i-g7baduaayq63y.eu-5.platformsh.site/static/
           https://main-bvxea6i-g7baduaayq63y.eu-5.platformsh.site/ is served by application \`app\`
           https://main-bvxea6i-g7baduaayq63y.eu-5.platformsh.site/static/ is served by application \`app\`

    `}
</DynamicCodeBlock>

## Preview your Magento project

Now that your Magento project has been successfully created, you will see the standard Magento layout when you navigate to your preview link:

<img src="https://mintcdn.com/upsun-c9761871/7cK3KMJBgO7MXm_y/images/guides/magento/preview-mage.png?fit=max&auto=format&n=7cK3KMJBgO7MXm_y&q=85&s=d3137aaea14ae8cc3649720949cb56b2" alt="Your magento project in preview mode" width="3020" height="1638" data-path="images/guides/magento/preview-mage.png" />

## Fetch your Magento project locally

First, get your project ID by clicking the three dots in the upper right hand of your console, next to the settings cog wheel. Your project ID will appear in a drop down menu.

<img src="https://mintcdn.com/upsun-c9761871/7cK3KMJBgO7MXm_y/images/guides/magento/project-id-mage-1.png?fit=max&auto=format&n=7cK3KMJBgO7MXm_y&q=85&s=05086025b9fd2776c6ce8e287a2a7502" alt="Your project ID in console" width="1472" height="270" data-path="images/guides/magento/project-id-mage-1.png" />

Copy the following command into your command line.

`upsun get <projectid>`

Make sure to replace `<projectid>` with the Project ID you just copied from console. Once you run the command in command line, you will be asked if you want to set the remote project for any existing repositories to your project. You will also need to specify a directory for your project to be stored in when downloaded.

Once your project has successfully downloaded, you will be able to access it locally by navigating to the directory you chose.

## Further resources

### Documentation

* [PHP documentation](/docs/languages/php/)
* [Authenticated Composer repositories](/docs/languages/php/composer-auth)
* [Resource configuration guide](/docs/manage-resources/adjust-resources)

### Authentication

* [Composer Authentication and Post Installation Setup](https://github.com/platformsh-templates/magentoCE24/blob/main/README#composer-authentication-and-post-installation-setup)
* [Magento Repository authentication keys](https://devdocs.magento.com/guides/v2.4/install-gde/prereq/connect-auth.html)

### Community content

* [PHP topics](https://support.platform.sh/hc/en-us/search?utf8=%E2%9C%93\&query=php)
