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

# Runtime operations

> Set up runtime operations to run one-off commands on your project through the Upsun CLI.

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 MoreIcon = () => <svg width="24px" height="24px" style={{
  display: 'inline',
  verticalAlign: 'middle'
}}>
    <title id="more-icon">More</title>
    <path d="M12 6.5C12.8284 6.5 13.5 5.82843 13.5 5C13.5 4.17157 12.8284 3.5 12 3.5C11.1716 3.5 10.5 4.17157 10.5 5C10.5 5.82843 11.1716 6.5 12 6.5Z" fill="current"></path>
    <path d="M12 13.5C12.8284 13.5 13.5 12.8284 13.5 12C13.5 11.1716 12.8284 10.5 12 10.5C11.1716 10.5 10.5 11.1716 10.5 12C10.5 12.8284 11.1716 13.5 12 13.5Z" fill="current"></path>
    <path d="M12 20.5C12.8284 20.5 13.5 19.8284 13.5 19C13.5 18.1716 12.8284 17.5 12 17.5C11.1716 17.5 10.5 18.1716 10.5 19C10.5 19.8284 11.1716 20.5 12 20.5Z" fill="current"></path>
  </svg>;

Runtime operations allow you to trigger one-off commands or scripts on your project.
Similar to [crons](/docs/configure-apps/image-properties/crons), they run in the app container but not on a specific schedule.
You can [define runtime operations](#define-a-runtime-operation) in your [app configuration](/docs/configure-apps/app-reference/single-runtime-image)
and [trigger them](#run-a-runtime-operation) at any time through the Upsun CLI.

For example, if you have a static website,
you may want to set up a runtime operation to occasionally fetch content from a backend system
without having to rebuild your whole app.

## Define a runtime operation

To define a runtime operation, add a configuration similar to the following:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        <APP_NAME>:
          source:
            root: "/"
          operations:
            <RUNTIME_OPERATION_NAME>:
              role: <USER_ROLE>
              commands:
                start: <COMMAND>`
  }
</DynamicCodeBlock>

When you define a runtime operation,
you can specify which users can trigger it according to their user `role`:

* `viewer`
* `contributor`
* `admin`

If you don't set the `role` option when configuring your runtime operation,
by default all users with the `contributor` role can trigger it.

For example, to allow admin users to clear the cache of a Drupal site,
you could define an operation like the following:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        <APP_NAME>:
          source:
            root: "/"
          operations:
            clear-rebuild:
              role: admin
              commands:
                start: drush cache:rebuild
      `}
</DynamicCodeBlock>

The name of the runtime operation in this case is `clear-rebuild`.

For more possibilities, see other [runtime operation examples](#runtime-operation-examples).

## Run a runtime operation

<Tabs>
  <Tab title="In the Console">
    First, make sure that you have [defined a runtime operation](#define-a-runtime-operation). Then:

    1. Navigate to the environment where you want to run the operation.
    2. Click <MoreIcon /> **More**.
    3. Click **Run runtime operation**.
    4. Select the operation you want to run.
    5. Click **Run**.
  </Tab>

  <Tab title="Using the CLI">
    A runtime operation can be triggered through the Upsun CLI once it has been [defined](#define-a-runtime-operation).

    Run the following command:

    ```bash theme={null}
    upsun operation:run <RUNTIME_OPERATION_NAME> --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```

    You can only trigger a runtime operation if you have permission to do so.
    Permissions are granted through the `role` option specified in the [runtime operation configuration](#define-a-runtime-operation). This can only be done if a [runtime operation has been defined](#define-a-runtime-operation).

    For example, to trigger the runtime operation [defined previously](#define-a-runtime-operation),
    you could run the following command:

    ```bash theme={null}
    upsun operation:run clear-rebuild --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```
  </Tab>
</Tabs>

## List your runtime operations

To list all the runtime operations available on an environment,
run the following command:

```bash theme={null}
upsun operation:list --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
```

## Runtime operation examples

### Build your app when using a static site generator

During every Upsun deployment, a standard [`build` step](/docs/core-concepts/build-deploy#the-build) is run.
When you use a static site generator like Gatsby
or Next.js with a headless backend
you need to run a second `build` step to get your app ready for production.

This is because, as its framework is being built,
your frontend needs to pull content-related data from your backend’s API
(to generate all the static HTML pages your site is to serve).
To accomplish this on Upsun, where each app goes through a build-deploy pipeline in parallel,
your frontend’s build must be delayed *until after* your backend has fully deployed.
It's often delayed up until the [`post_deploy` hook](/docs/configure-apps/hooks/hooks-comparison#post-deploy-hook) stage,
when the filesystem is read-only.

You can use a runtime operation to trigger the second `build` step
after the initial deployment of your app or after a redeployment.
You can also trigger it when you need to fetch content from your backend
but want to avoid going through the whole Upsun [build and deploy processes](/docs/core-concepts/build-deploy) again.

<Note>
  The following examples assume that the frontend and backend containers are included in the same environment.
  This isn’t necessary for the commands to run successfully.<br />
  What *is* necessary is that the build destination for your frontend **is  writable at runtime**
  (meaning, you must [define a mount](/docs/configure-apps/image-properties/mounts) for it).
  If you don’t want to include a build within a mount (especially if your data source **isn’t** on Upsun),
  you can use [source operations](/docs/configure-apps/source-operations) to achieve a similar effect,
  but through generating a new commit.
</Note>

<Tabs>
  <Tab title="Gatsby">
    To run the [Gatsby build](https://www.gatsbyjs.com/docs/conceptual/overview-of-the-gatsby-build-process/#understanding-gatsby-build-build-time) step,
    define a runtime operation similar to the following:

    ```yaml .upsun/config.yaml theme={null}
    applications:
      # The name of the app container. Must be unique within a project.
      myapp:
        # The location of the application's code.

        operations:
          gatsby-build:
            role: viewer
            commands:
              start: gatsby build
    ```

    To trigger your runtime operation, run a command similar to the following:

    ```bash theme={null}
    upsun operation:run gatsby-build --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```
  </Tab>

  <Tab title="Next.js">
    To run the [Next.js build](https://nextjs.org/docs/deployment#nextjs-build-api) step,
    define a runtime operation similar to the following:

    ```yaml .upsun/config.yaml theme={null}
    applications:
      # The name of the app container. Must be unique within a project.
      myapp:
        # The location of the application's code.

        operations:
          next-build:
            role: admin
            commands:
              # All below are valid, depending on your setup
              start: next build
              # start: npx next build
              # start: npm run build
    ```

    To trigger your runtime operation, run a command similar to the following:

    ```bash theme={null}
    upsun operation:run next-build --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```
  </Tab>
</Tabs>

### Execute actions on your Node.js app

You can define runtime operations for common [pm2](https://pm2.io/docs/runtime/overview/) process manager tasks.

<Tabs>
  <Tab title="Ping your app">
    To ping your Node.js app, define a runtime operation similar to the following:

    ```yaml .upsun/config.yaml theme={null}
    applications:
      # The name of the app container. Must be unique within a project.
      myapp:
        # The location of the application's code.

        operations:
          pm2-ping:
            role: admin
            commands:
              start: |
                # Assuming pm2 start npm --no-daemon --watch --name $APP -- start -- -p $PORT
                APP=$(cat package.json | jq -r '.name')
                pm2 ping $APP
    ```

    To trigger your runtime operation, run a command similar to the following:

    ```bash theme={null}
    upsun operation:run pm2-ping --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```
  </Tab>

  <Tab title="Reload your app">
    To reload your Node.js app, define a runtime operation similar to the following:

    ```yaml .upsun/config.yaml theme={null}
    applications:
      # The name of the app container. Must be unique within a project.
      myapp:
        # The location of the application's code.

        operations:
          pm2-reload:
            role: admin
            commands:
              start: |
                # Assuming pm2 start npm --no-daemon --watch --name $APP -- start -- -p $PORT
                APP=$(cat package.json | jq -r '.name')
                pm2 reload $APP
    ```

    To trigger your runtime operation, run a command similar to the following:

    ```bash theme={null}
    upsun operation:run pm2-reload --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```
  </Tab>

  <Tab title="Restart your app">
    To restart your Node.js app, define a runtime operation similar to the following:

    ```yaml .upsun/config.yaml theme={null}
    applications:
      # The name of the app container. Must be unique within a project.
      myapp:
        # The location of the application's code.

        operations:
          pm2-restart:
            role: admin
            commands:
              start: |
                # Assuming pm2 start npm --no-daemon --watch --name $APP -- start -- -p $PORT
                APP=$(cat package.json | jq -r '.name')
                pm2 restart $APP
    ```

    To trigger your runtime operation, run a command similar to the following:

    ```bash theme={null}
    upsun operation:run pm2-restart --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
    ```
  </Tab>
</Tabs>

### Define management commands on your Django project

On a Django project, you can [define custom `django-admin` commands](https://docs.djangoproject.com/en/4.2/howto/custom-management-commands/), for example to run a one-off management command (`manual migration` in the example above) outside of the Django ORM migration framework.
To do so, define a runtime operation similar to the following:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {
      `applications:
        <APP_NAME>:
          source:
            root: "/"
          type: python:{{version:python:latest}}
          operations:
            manual-migration:
              role: admin
              commands:
                start: python manage.py manual_migration`
  }
</DynamicCodeBlock>

To trigger your runtime operation, run a command similar to the following:

```bash theme={null}
upsun operation:run manual-migration --project <PROJECT_ID> --environment <ENVIRONMENT_NAME>
```
