Робота з Docker

нотатка

Неможливо створити reflinks або hardlinks між контейнером Docker і головною файловою системою під час збирання. Наступне найкраще, що ви можете зробити, це використовувати монтування кешу BuildKit для спільного використання кешу між збірками. Крім того, ви можете використовувати podman, оскільки він може монтувати томи Btrfs під час збирання. If you use BuildKit cache mounts, keep the pnpm store cache scoped to mutually trusted builds. A store cache that can be written by an untrusted build should not be reused by trusted builds.

Official pnpm base image#

An official pnpm base image is published to GitHub Container Registry as ghcr.io/pnpm/pnpm. It is based on debian:stable-slim and contains only the pnpm standalone binary. Node.js is not bundled. This lets you pick the Node.js version yourself (inside your Dockerfile or at runtime) instead of being locked to whatever Node version a base image ships with.

Позначки#

Тег Значення
<version> Exact, immutable (e.g. 11.0.0). Includes prereleases.
<major> Tracks the latest stable release within that major (e.g. 11).
latest Highest stable pnpm version. Not updated for prereleases.

Supported platforms: linux/amd64, linux/arm64.

Installing Node.js#

Let pnpm install Node.js automatically from devEngines.runtime in your package.json:

package.json
{
  "devEngines": {
    "runtime": {
      "name": "node",
      "version": "22.x",
      "onFail": "download"
    }
  }
}

This example assumes the project has a pnpm-workspace.yaml. If it does not, use COPY package.json pnpm-lock.yaml ./ for the first copy step.

Dockerfile
FROM ghcr.io/pnpm/pnpm:11
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
CMD ["pnpm", "start"]

If your project has a pnpm-workspace.yaml, copy it before running pnpm install. It contains project settings and, in a workspace, the package patterns. Omitting it can change how dependencies are installed or make a frozen install fail. Omit it from the COPY instruction only if your project has no such file.

When to use this image#

  • You want the Node.js version to be pinned by your project via devEngines.runtime rather than by the base image.
  • You want to upgrade pnpm and Node.js independently.
  • You prefer a minimal Debian base without the Node.js build toolchain.

The recipes further down this page start from this image and let pnpm install Node.js. If you prefer your own Node.js base image, keep the rest of each recipe and install pnpm into that image instead.

Мінімізація розміру образу Docker і часу збірки#

  • Use a small image, e.g. ghcr.io/pnpm/pnpm or node:XX-slim.
  • Use multi-stage if possible and makes sense.
  • Use BuildKit cache mounts.

The recipes below use the official pnpm image, which already sets PNPM_HOME=/pnpm and puts /pnpm/bin on PATH, so the default store is at /pnpm/store.

When the base image has a runtime installed with pnpm runtime set node 24 -g, keep the build cache at a separate location, such as /var/cache/pnpm. Mounting a cache over /pnpm/store hides the managed runtime for the duration of the RUN instruction. The examples below set --store-dir to the cache mount.

Приклад 1: Створення пакета в контейнері Docker#

Оскільки devDependencies потрібні лише для збирання пакунка, pnpm install --prod буде етапом, окремим від pnpm install і pnpm run build, що дозволить останньому етапу скопіювати лише необхідні файли з попередніх етапів, мінімізуючи розмір кінцевого образу.

.dockerignore
node_modules
.git
.gitignore
*.md
dist
Dockerfile
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g
COPY . /app
WORKDIR /app

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/var/cache/pnpm pnpm install --store-dir /var/cache/pnpm --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/var/cache/pnpm pnpm install --store-dir /var/cache/pnpm --frozen-lockfile
RUN pnpm run build

FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]

Приклад 2: Створення кількох образів Docker у монорепо#

Припустимо, у вас є монорепо з 3 пакунками: app1, app2 та common; app1 та app2 залежать від common, але не один від одного.

Якщо ви хочете зберегти лише необхідні залежності для кожного пакунка, pnpm deploy допоможе вам скопіювати лише необхідні файли та пакунки.

Structure of the monorepo
./
├── Dockerfile
├── .dockerignore
├── .gitignore
├── packages/
│   ├── app1/
│   │   ├── dist/
│   │   ├── package.json
│   │   ├── src/
│   │   └── tsconfig.json
│   ├── app2/
│   │   ├── dist/
│   │   ├── package.json
│   │   ├── src/
│   │   └── tsconfig.json
│   └── common/
│       ├── dist/
│       ├── package.json
│       ├── src/
│       └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── tsconfig.json
pnpm-workspace.yaml
packages:
  - 'packages/*'
syncInjectedDepsAfterScripts:
- build
injectWorkspacePackages: true
.dockerignore
node_modules
.git
.gitignore
*.md
dist
Dockerfile
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g

FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN --mount=type=cache,id=pnpm,target=/var/cache/pnpm pnpm install --store-dir /var/cache/pnpm --frozen-lockfile
RUN pnpm run -r build
RUN pnpm deploy --filter=app1 --prod /prod/app1
RUN pnpm deploy --filter=app2 --prod /prod/app2

FROM base AS app1
COPY --from=build /prod/app1 /prod/app1
WORKDIR /prod/app1
EXPOSE 8000
CMD [ "pnpm", "start" ]

FROM base AS app2
COPY --from=build /prod/app2 /prod/app2
WORKDIR /prod/app2
EXPOSE 8001
CMD [ "pnpm", "start" ]

Запустіть наступні команди для створення образів для застосунків app1 та app2:

docker build . --target app1 --tag app1:latest
docker build . --target app2 --tag app2:latest

Приклад 3: Збірка на CI/CD#

У середовищах CI або CD монтування кешу BuildKit може бути недоступним, оскільки віртуальна машина або контейнер є тимчасовими, і працюватиме лише звичайний кеш докера.

So an alternative is to use a typical Dockerfile with layers that are built incrementally, for this scenario, pnpm fetch is the best option, as it only needs the lockfile and the project settings, and the layer cache will only be lost when you change the lockfile or the project settings.

pnpm fetch only fills the virtual store. A pnpm install --offline after copying the sources links the dependencies into node_modules. The build stage installs every dependency, and a separate stage installs only the production dependencies, so the final image does not carry the dev dependencies.

This example assumes the project has a pnpm-workspace.yaml. If it does not, use COPY pnpm-lock.yaml /app/ instead. If you patched any package, also copy the patches directory before running pnpm fetch.

Dockerfile
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g
WORKDIR /app

FROM base AS build
COPY pnpm-lock.yaml pnpm-workspace.yaml /app/
RUN pnpm fetch
COPY . /app
RUN pnpm install --offline --frozen-lockfile
RUN pnpm run build

FROM base AS prod-deps
COPY pnpm-lock.yaml pnpm-workspace.yaml /app/
RUN pnpm fetch --prod
COPY . /app
RUN pnpm install --offline --frozen-lockfile --prod

FROM base
COPY package.json /app/
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]