pnpm/action-setup Integration and Orchestration

The integration of pnpm into continuous integration and continuous deployment (CI/CD) pipelines, specifically within the GitHub Actions ecosystem, represents a critical shift toward more efficient dependency management. By leveraging a content-addressable store, pnpm fundamentally alters how packages are handled compared to traditional npm or yarn installations. In the context of GitHub Actions, the primary mechanism for this integration is the pnpm/action-setup action, which provides a standardized method for installing the pnpm CLI, managing specific versions, and optimizing the build lifecycle through sophisticated caching strategies.

The necessity of a dedicated setup action arises from the fact that GitHub-hosted runners typically come with npm pre-installed, but not pnpm. Manually installing pnpm via npm install -g pnpm in every job is inefficient and lacks version pinning, which can lead to non-deterministic builds. The pnpm/action-setup action solves this by ensuring the exact version of the package manager is present before the installation phase begins, allowing developers to maintain parity between local development environments and the remote CI runner.

High-Level Configuration of pnpm/action-setup

The core of implementing pnpm in a GitHub Actions workflow is the utilization of the pnpm/action-setup action. This action is designed to prepare the environment by installing the pnpm CLI and optionally triggering the initial dependency installation.

The basic implementation follows a specific sequence within the .github/workflows/NAME.yml file. A standard deployment requires the checkout of the repository, the setup of the pnpm CLI, and the configuration of the Node.js environment.

yaml name: pnpm Example Workflow on: push: jobs: build: runs-on: ubuntu-24.04 strategy: matrix: node-version: [24] steps: - uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 with: version: 11 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "pnpm" - name: Install dependencies run: pnpm install

In this configuration, the version: 11 parameter ensures that the environment uses a specific major version of pnpm. If this parameter is omitted, the action can automatically detect the required version if the packageManager field is correctly defined within the project's package.json file. This creates a seamless link between the project metadata and the CI infrastructure, reducing the risk of version mismatch errors.

Advanced Caching Strategies and Store Management

One of the most significant challenges in CI environments is the time spent downloading dependencies. While actions/setup-node provides a basic cache: "pnpm" option, some users have reported that the standard template provided in the documentation does not always function as expected. To achieve maximum efficiency and reliability, a manual caching approach using the pnpm store directory is recommended.

The failure of standard caching often stems from the dynamic nature of the pnpm store path. To resolve this, developers must explicitly retrieve the store path and pass it to the actions/cache action.

The following sequence provides a robust implementation for caching:

yaml steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - name: 🤌 Setup pnpm uses: 'pnpm/action-setup@v4' with: version: 9.0.4 run_install: false - name: ⎔ Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: ⎔ Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: ⎔ Setup node uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - name: 📥 Download deps run: pnpm install --frozen-lockfile

The logic applied here involves several critical layers:

  1. The run_install: false flag prevents the setup action from running an immediate installation, allowing the cache to be restored first.
  2. The command pnpm store path --silent retrieves the absolute path to the pnpm content-addressable store on the runner.
  3. This path is written to the $GITHUB_ENV file, making it available as a variable for subsequent steps.
  4. The actions/cache action uses a composite key based on the runner's operating system and a hash of the pnpm-lock.yaml file. This ensures that if the lockfile changes, a new cache is generated, while remaining consistent across unchanged builds.
  5. The use of --frozen-lockfile during the pnpm install phase is mandatory for CI. It prevents the package manager from updating the lockfile, ensuring that the exact versions specified during development are the ones installed in the pipeline.

Specialized Implementations for Expo and React Native

Integrating pnpm with Expo in GitHub Actions presents a unique set of challenges because Expo's remote build environments often default to npm. This leads to a conflict where the project is configured for pnpm, but the Expo build process attempts to run npm install, triggering errors from the only-allow package which is designed to enforce the use of pnpm.

To force Expo to recognize and utilize pnpm, a multi-pronged configuration approach is required.

The first method involves the .npmrc file. Adding a specific configuration to the project root forces the environment to acknowledge pnpm.

  • In the project root, create or edit the .npmrc file and add the line use-pnpm=true.
  • In the GitHub Actions workflow, explicitly ensure this setting is present before the Expo steps run:

yaml - run: echo "use-pnpm=true" >> .npmrc

The second method utilizes the eas.json configuration, which governs the EAS (Expo Application Services) build process. By injecting an environment variable into the build profile, the Expo remote environment can be instructed to switch package managers.

Within the eas.json file, the EXPO_USE_PNPM variable must be set to 1:

json { "build": { "preview": { "android": { "buildType": "apk" }, "ios": { "simulator": true }, "env": { "EXPO_USE_PNPM": "1" } } } }

Furthermore, this environment variable must be mirrored in the GitHub Actions workflow file to ensure consistency across the local runner and the remote build server:

yaml env: EXPO_USE_PNPM: 1

This dual-layer configuration (both in eas.json and the workflow env block) effectively overrides the default npm behavior and allows the Expo build process to successfully leverage the pnpm installation.

Customizable Installation and Dependency Arguments

The pnpm/action-setup action offers flexibility beyond simple installation. It allows for complex installation arguments and the management of global packages, which is essential for tools like TypeScript, Prettier, or Gulp that may need to be available globally on the runner.

For projects requiring a recursive installation across a monorepo or specific flags for peer dependency handling, the run_install parameter can be expanded into an object.

Example of a detailed installation configuration:

yaml - uses: pnpm/action-setup@v4 with: version: 8 run_install: | - recursive: true args: [--frozen-lockfile, --strict-peer-dependencies] - args: [--global, gulp, prettier, typescript]

In this configuration:
- The recursive: true flag tells pnpm to install dependencies across all workspace members in a monorepo.
- The args array allows for the passing of specific CLI flags. --frozen-lockfile ensures build reproducibility.
- --strict-peer-dependencies forces the build to fail if peer dependencies are not met, preventing runtime crashes in production.
- A separate args block is used to install global utilities, ensuring the environment has the necessary tooling for linting and compilation.

Comparative Analysis of pnpm Integration Across CI Platforms

While GitHub Actions is a primary focus, pnpm's integration patterns are consistent across other major CI providers. Understanding these patterns helps in migrating workflows or maintaining hybrid CI setups.

CircleCI Configuration

On CircleCI, pnpm is managed through the .circleci/config.yml file. Unlike GitHub Actions, which uses a dedicated action, CircleCI requires a manual setup using Corepack.

yaml version: 2.1 jobs: build: docker: - image: node:18 resource_class: large parallelism: 10 steps: - checkout - restore_cache: name: Restore pnpm Package Cache keys: - pnpm-packages-{{ checksum "pnpm-lock.yaml" }} - run: name: Install pnpm package manager command: | npm install --global corepack@latest corepack enable corepack prepare pnpm@latest-11 --activate pnpm config set store-dir .pnpm-store - run: name: Install Dependencies command: | pnpm install - save_cache: name: Save pnpm Package Cache key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }} paths: - .pnpm-store

The critical difference here is the use of Corepack, a Node.js tool that manages package manager versions. By running corepack enable and corepack prepare, the environment ensures that the specific version of pnpm is activated.

GitLab CI Configuration

GitLab CI utilizes the .gitlab-ci.yml file and relies on a before_script block to prepare the environment.

```yaml
stages:
- build

build:
stage: build
image: node:24.14.1
before_script:
- npm install --global corepack@latest
- corepack enable
- corepack prepare pnpm@latest-11 --activate
- pnpm config set store-dir .pnpm-store
script:
- pnpm install
cache:
key:
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
```

In GitLab, the cache is managed at the job level, where the pnpm-lock.yaml file serves as the key to determine when the cache should be invalidated.

Jenkins Pipeline

Jenkins utilizes a Groovy-based DSL for its pipeline definitions. The pnpm setup is handled within a sh block inside a Docker agent.

groovy pipeline { agent { docker { image 'node:lts-bullseye-slim' args '-p 3000:3000' } } stages { stage('Build') { steps { sh 'npm install --global corepack@latest' sh 'corepack enable' sh 'corepack prepare pnpm@latest-11 --activate' sh 'pnpm install' } } } }

Semaphore CI

Semaphore uses a YAML configuration in .semaphore/ lsemaphore.yml and handles caching through a dedicated cache restore and cache store mechanism.

yaml version: v1.0 name: Semaphore CI pnpm example agent: machine: type: e1-standard-2 os_image: ubuntu2404 blocks: - name: Install dependencies task: jobs: - name: pnpm install commands: - npm install --global corepack@latest - corepack enable - corepack prepare pnpm@latest-11 --activate - checkout - cache restore node-$(checksum pnpm-lock.yaml) - pnpm install - cache store node-$(checksum pnpm-lock.yaml) $(pnpm store path)

Technical Summary of pnpm Setup Components

The following table provides a structured comparison of the components involved in the pnpm setup process across different environments.

Component GitHub Actions CircleCI GitLab CI Jenkins
Primary Setup Method pnpm/action-setup Corepack via npm Corepack via before_script Shell commands in Pipeline
Cache Mechanism actions/cache restore_cache/save_cache cache: paths Manual/Plugin based
Versioning version input corepack prepare corepack prepare corepack prepare
Store Path Handling Dynamic via pnpm store path Manual .pnpm-store config Manual .pnpm-store config Default pnpm path
Lockfile Enforcement --frozen-lockfile pnpm install pnpm install pnpm install

Conclusion

The transition to pnpm within GitHub Actions, while offering significant performance gains, requires a deliberate approach to configuration, particularly regarding the installation of the CLI and the management of the content-addressable store. The pnpm/action-setup action serves as the foundational block, but the true optimization lies in the manual orchestration of the store path and the use of actions/cache to ensure that the dependency layer does not become a bottleneck in the CI pipeline.

Furthermore, the integration with specialized frameworks like Expo highlights the necessity of overriding default environment behaviors using .npmrc and specific environment variables such as EXPO_USE_PNPM. By combining the pnpm/action-setup action with precise cache keys and explicit environment configurations, developers can achieve a deterministic, fast, and scalable build process that leverages the full potential of the pnpm ecosystem. The use of Corepack across other platforms like CircleCI and GitLab CI underscores a broader industry trend toward standardized package manager versioning, moving away from the unpredictability of global npm installations.

Sources

  1. pnpm.io Continuous Integration
  2. GitHub Issue 1972 - pnpm/pnpm
  3. Theodorus Clarence - pnpm GitHub Actions Cache
  4. Latenode Community - Configure Expo to use pnpm in GitHub Actions
  5. CiCube Workflow Hub - pnpm-action-setup

Related Posts