도커 작업

참고

빌드 시간 동안에 도커 컨테이너와 호스트 파일 시스템 간에 참조 링크 또는 하드 링크를 생성하는 것은 불가능합니다. 다음으로 할 수 있는 가장 좋은 방법은 BuildKit 캐시 마운트를 사용하여 빌드 간에 캐시를 공유하는 것입니다. 또는, podman을 사용할 수 있는데, podman은 빌드 시간에 Btrfs 볼륨을 마운트할 수 있기 때문입니다. BuildKit 캐시 마운트를 사용하는 경우, pnpm 스토어 캐시는 서로 신뢰할 수 있는 빌드 범위로 제한하세요. 신뢰할 수 없는 빌드가 쓸 수 있는 스토어 캐시는 신뢰할 수 있는 빌드에서 재사용해서는 안 됩니다.

공식 pnpm 베이스 이미지#

공식 pnpm 기본 이미지는 GitHub Container Registry에 ghcr.io/pnpm/pnpm으로 게시됩니다. It is based on debian:stable-slim and contains only the pnpm standalone binary. Node.js is not bundled. 따라서 베이스 이미지에 포함된 Node 버전에 묶이지 않고 (Dockerfile 내부 또는 런타임에) Node.js 버전을 직접 선택할 수 있습니다.

태그#

태그 의미
<version> 정확하며 변경 불가능합니다(예: 11.0.0). 사전 릴리스를 포함합니다.
<major> 해당 메이저 버전 내의 최신 안정 릴리스를 추적합니다(예: 11).
latest Highest stable pnpm version. 사전 릴리스에서는 업데이트되지 않습니다.

지원되는 플랫폼: linux/amd64, linux/arm64.

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.

이 이미지를 사용해야 하는 경우#

  • You want the Node.js version to be pinned by your project via devEngines.runtime rather than by the base image.
  • 독립적으로 pnpm과 Node.js를 업그레이드하고 싶은 경우.
  • Node.js 빌드 도구 체인이 없는 최소한의 Debian 베이스를 선호하는 경우.

이 페이지 아래의 레시피는 이 이미지에서 시작하여 pnpm이 Node.js를 설치하도록 합니다. 자체 Node.js 기본 이미지를 선호한다면, 각 레시피의 나머지 부분은 그대로 두고 대신 해당 이미지에 pnpm을 설치하세요.

도커 이미지 크기 및 빌드 시간 최소화#

  • 작은 이미지를 사용하세요. 예: ghcr.io/pnpm/pnpm 또는 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: 도커 컨테이너에서 번들 빌드#

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: 모노리포에서 다중 도커 이미지 빌드#

App1, app2 및 common이라는 3개의 패키지가 있는 모노레포가 있다고 가정합니다. 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 환경에서는 VM이나 컨테이너가 일시적이고 일반 Docker 캐시만 작동하기 때문에 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" ]