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

# Manage Node.js versions

> See how to manage different Node.js versions in your Upsun containers."

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 DisclaimerNix = () => <Tip>
    You can now use composable image to install runtimes and tools in your application container. To find out more, see the <a href="/docs/configure-apps/app-reference/composable-image">Composable image</a> topic.
  </Tip>;

<DisclaimerNix />

Each Upsun container image includes a specific language in a specific version.
A set of dependencies is also provided based on that language version.
This ensures that your application container is as small and efficient as possible.

Therefore, by default, when you use an Upsun container image,
you use the Node.js version that's included in that image, if any.

If you want to use a different Node.js version, use a version manager to install it yourself.
You can use one of the following version managers:

* [Use `n`](#use-n)
* [Use `nvm`](#use-nvm)

Both of the recommendations use a `.nvmrc` file to specify the desired Node.js version.
You could also specify a different file or use [environment variables](/docs/development/variables).

## Use `n`

The [`n` package](https://github.com/tj/n) works for various Unix-like systems,
including Windows Subsystem for Linux.

1. Add the desired Node.js version to your environment using `.nvmrc`, `.n-node-version`, `.node-version`, or `package.json`.

<Tabs>
  <Tab title=".nvmrc">
    Create a `.nvmrc` file in [your app root](/docs/configure-apps/app-reference/single-runtime-image#root-directory):

    ```yaml .nvmrc theme={null}
    v16.13.2
    ```
  </Tab>

  <Tab title=".n-node-version/.node-version">
    Create a `.n-node-version` or `.node-version` file in [your app root](/docs/configure-apps/app-reference/single-runtime-image#root-directory):

    ```yaml .n-node-version or .node-version theme={null}
    16.13.2
    ```
  </Tab>

  <Tab title="package.json">
    Add an `engines.node` property to your `package.json`.
    This property accepts either an exact version or a range:

    ```json package.json theme={null}
    {
      "engines": {
        "node": ">=0.10.3 <15"
      }
    }
    ```
  </Tab>
</Tabs>

2. Add it as a dependency:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          dependencies:
            nodejs:
              n: "*"`
  }
</DynamicCodeBlock>

Adding it as a dependency ensures it's cached for future builds.

3. Set the location of the `n` files using the `N_PREFIX` environment variable:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          dependencies:
            nodejs:
              n: "*"
          variables:
            env:
              N_PREFIX: /app/.global`
  }
</DynamicCodeBlock>

4. Install the specified version of Node.js in a [`build` hook](/docs/configure-apps/hooks/hooks-comparison#build-hook):

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          dependencies:
            nodejs:
              n: "*"
          variables:
            env:
              N_PREFIX: /app/.global
          hooks:
            build: |
              # Exit the hook on any failure
              set -e

              # Install the version specified in the .nvmrc file
              n auto

              # Reset the location hash to recognize the newly installed version
              hash -r`
  }
</DynamicCodeBlock>

Now your hooks should be able to use the specified version of Node.js.
You can verify this by running `node -v`.

Your final app configuration should look something like this:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          dependencies:
            nodejs:
              n: "*"
          variables:
            env:
              N_PREFIX: /app/.global
          hooks:
            build: |
              # Exit the hook on any failure
              set -e

              # Install the version specified in the .nvmrc file
              n auto

              # Reset the location hash to recognize the newly installed version
              hash -r`
  }
</DynamicCodeBlock>

## Use `nvm`

[Node Version Manager (`nvm`)](https://github.com/nvm-sh/nvm) is a bash script for managing Node.js versions.

You can use it to:

* Make a specific version available in the build and optionally the runtime container.
* Control the specific versions to be installed with [environment variables](/docs/development/variables),
  meaning you can also have different versions in different environments.

To use `nvm`, follow these steps:

1. Define which `nvm` version to use using an [environment variable](/docs/development/variables).
   Add it to your [app configuration](/docs/configure-apps):

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          variables:
            env:
              # Update for your desired NVM version.
              NVM_VERSION: v0.39.3`
  }
</DynamicCodeBlock>

2. Define your desired Node.js version using an environment variable.

   For your base version, set it in your app configuration:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          variables:
            env:
              # Update these for your desired NVM and Node versions.
              NVM_VERSION: v0.39.3
              NODE_VERSION: v18.14.2`
  }
</DynamicCodeBlock>

To get different versions in different environments, [set environment-specific variables](/docs/development/variables/set-variables#create-environment-specific-variables).

3. Add a `.nvm` directory to your cache in your [build hook](/docs/configure-apps/hooks):

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          variables:
            env:
              # Update these for your desired NVM and Node versions.
              NVM_VERSION: v0.39.3
              NODE_VERSION: v18.14.2
          hooks:
            build: |
              set -e
              unset NPM_CONFIG_PREFIX
              export NVM_DIR="$PLATFORM_APP_DIR/.nvm"

              # Link cache with app
              if [ ! -d "$PLATFORM_CACHE_DIR/.nvm" ]; then
                  mkdir -p $PLATFORM_CACHE_DIR/.nvm
              fi
              ln -s $PLATFORM_CACHE_DIR/.nvm $NVM_DIR`
  }
</DynamicCodeBlock>

<Note>
  Instead of using a symlink between your cache and application directories,
  you might need to copy the content of `$PLATFORM_CACHE_DIR/.nvm` into `$PLATFORM_APP_DIR/.nvm` manually.
  To do so, run the following command:

  ```bash theme={null}
  rsync -av $PLATFORM_CACHE_DIR/.nvm $PLATFORM_APP_DIR
  ```
</Note>

4. Use the cache directory and install based on the variables if not present:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          variables:
            env:
              # Update these for your desired NVM and Node versions.
              NVM_VERSION: v0.39.3
              NODE_VERSION: v18.14.2
          hooks:
            build: |
              ...
              # Check for Node.js version and install if not present
              if [ ! -d "$PLATFORM_CACHE_DIR/.nvm/versions/node/$NODE_VERSION" ]; then

                # Get nvm install script if correct version not present
                export NVM_INSTALL_FILE="\${PLATFORM_CACHE_DIR}/nvm_\${NVM_VERSION}_install.sh"
                if [ ! -f "$NVM_INSTALL_FILE" ]; then
                    wget -nc -O "$NVM_INSTALL_FILE" "https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh"
                fi

                # Install, automatically using NODE_VERSION
                bash $NVM_INSTALL_FILE
              fi

              # Activate nvm
              [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"

              # Use the specified version
              nvm use "$NODE_VERSION"`
  }
</DynamicCodeBlock>

5. Optional: To use the specified Node.js version in the runtime container and not just the build,
   activate `nvm` via [script](/docs/development/variables/set-variables#set-variables-via-script):

   ```bash .environment theme={null}
   unset NPM_CONFIG_PREFIX
   export NVM_DIR="$PLATFORM_APP_DIR/.nvm"
   [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
   ```

   Your final app configuration should look something like the following:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          variables:
            env:
              # Update these for your desired NVM and Node versions.
              NVM_VERSION: v0.39.3
              NODE_VERSION: v18.14.2
          hooks:
            build: |
              set -e
              unset NPM_CONFIG_PREFIX
              export NVM_DIR="$PLATFORM_APP_DIR/.nvm"

              # Link cache with app
              if [ ! -d "$PLATFORM_CACHE_DIR/.nvm" ]; then
                mkdir -p $PLATFORM_CACHE_DIR/.nvm
              fi
              ln -s $PLATFORM_CACHE_DIR/.nvm $NVM_DIR
              # Check for Node.js version and install if not present
              if [ ! -d "$PLATFORM_CACHE_DIR/.nvm/versions/node/$NODE_VERSION" ]; then

                # Get nvm install script if correct version not present
                export NVM_INSTALL_FILE="\${PLATFORM_CACHE_DIR}/nvm_\${NVM_VERSION}_install.sh"
                if [ ! -f "$NVM_INSTALL_FILE" ]; then
                    wget -nc -O "$NVM_INSTALL_FILE" "https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh"
                fi

                # Install, automatically using NODE_VERSION
                bash $NVM_INSTALL_FILE
              fi

              # Activate nvm
              [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"

              # Use the specified version
              nvm use "$NODE_VERSION"`
  }
</DynamicCodeBlock>
