도커 작업

참고

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

공식 pnpm 베이스 이미지#

공식 pnpm 베이스 이미지는 GitHub Container Registry에 ghcr.io/pnpm/pnpm으로 게시됩니다. 이 이미지는 debian:stable-slim을 기반으로 하며 pnpm 독립 실행형 바이너리만 포함합니다 — Node.js는 번들에 포함되어 있지 않습니다. 따라서 베이스 이미지에 포함된 Node 버전에 묶이지 않고 (Dockerfile 내부 또는 런타임에) Node.js 버전을 직접 선택할 수 있습니다.

태그#

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

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

Node.js 설치하기#

글로벌 플래그와 함께 pnpm runtime set을 사용하여 이후 레이어와 런타임에서 node 바이너리를 PATH에서 찾을 수 있도록 하세요:

FROM ghcr.io/pnpm/pnpm:11
RUN pnpm runtime set node 22 -g
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
CMD ["node", "index.js"]

또는 devEngines.runtime를 package.json에 지정하여 pnpm이 Node.js를 자동으로 설치하도록 할 수 있습니다:

package.json
{
  "devEngines": {
    "runtime": {
      "name": "node",
      "version": "22.x",
      "onFail": "download"
    }
  }
}
FROM ghcr.io/pnpm/pnpm:11
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
CMD ["pnpm", "start"]

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

  • Node.js 버전을 베이스 이미지가 아닌 프로젝트(pnpm runtime set 또는 devEngines.runtime를 통해)에서 고정하고 싶은 경우.
  • 독립적으로 pnpm과 Node.js를 업그레이드하고 싶은 경우.
  • Node.js 빌드 도구 체인이 없는 최소한의 Debian 베이스를 선호하는 경우.

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

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

  • 작은 이미지를 사용하세요. 예: ghcr.io/pnpm/pnpm 또는 node:XX-slim.
  • 가능하고 합리적인 경우 다중 단계를 활용하세요.
  • BuildKit 캐시 마운트를 활용하세요.

아래 레시피는 공식 pnpm 이미지를 사용합니다. 이 이미지는 이미 PNPM_HOME=/pnpm을 설정하고 /pnpm/bin을 PATH에 추가하므로, 캐시 마운트 대상인 스토어는 /pnpm/store에 있습니다.

예시 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=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --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=/pnpm/store pnpm install --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이나 컨테이너가 일시적이므로 BuildKit 캐시 마운트를 사용할 수 없고 일반 docker 캐시만 작동할 수 있습니다.

따라서 대안은 점진적으로 빌드되는 레이어를 가진 일반적인 Dockerfile을 사용하는 것입니다. 이 시나리오에서는 pnpm fetch가 가장 좋은 선택입니다. pnpm-lock.yaml 파일만 필요하며, 종속성을 변경할 때만 레이어 캐시가 무효화되기 때문입니다.

Dockerfile
FROM ghcr.io/pnpm/pnpm:11 AS base

RUN pnpm runtime set node 24 -g

FROM base AS prod

WORKDIR /app
COPY pnpm-lock.yaml /app
RUN pnpm fetch --prod

COPY . /app
RUN pnpm run build

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