-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from depot/nodejs-guide
Best practice Dockerfile for Node.js with pnpm
- Loading branch information
Showing
2 changed files
with
130 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
--- | ||
title: Best practice Dockerfile for Node.js with pnpm | ||
ogTitle: Best practice Dockerfile for Node.js with pnpm | ||
description: A sample best practice pnpm Dockerfile for Node.js from us at Depot | ||
--- | ||
|
||
# Best practice Dockerfile for Node.js with pnpm | ||
|
||
Below is an example `Dockerfile` that we use and recommend at Depot when we are building Docker images for Node applications that use `pnpm` as their package manager. | ||
|
||
```dockerfile | ||
FROM node:20 AS base | ||
|
||
FROM base AS deps | ||
|
||
RUN corepack enable | ||
WORKDIR /app | ||
COPY package.json pnpm-lock.yaml ./ | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --prod | ||
|
||
FROM base AS build | ||
|
||
RUN corepack enable | ||
WORKDIR /app | ||
COPY package.json pnpm-lock.yaml ./ | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile | ||
COPY . . | ||
RUN pnpm build | ||
|
||
FROM base | ||
|
||
WORKDIR /app | ||
COPY --from=deps /app/node_modules /app/node_modules | ||
COPY --from=build /app/dist /app/dist | ||
ENV NODE_ENV production | ||
CMD ["node", "./dist/index.js"] | ||
|
||
``` | ||
|
||
## Explanation of the Dockerfile | ||
|
||
There are several things in this example Dockerfile that are worth calling out. Most notably, we use a multi-stage build to separate the installation of dependencies from the actual build of the application. This allows us to take advantage of Docker's layer caching to speed up our builds. | ||
|
||
#### Stage 1: `FROM node:20 AS base` | ||
|
||
```dockerfile | ||
FROM node:20 AS base | ||
``` | ||
|
||
Here we use the `node:20` base image and set the stage name to be reused in stages that follow. If we had common dependencies that we wanted to be accessible in any stages using this `base` stage, we could install them here. | ||
|
||
#### Stage 2: `FROM base AS deps` | ||
|
||
```dockerfile | ||
FROM base AS deps | ||
|
||
RUN corepack enable | ||
WORKDIR /app | ||
COPY package.json pnpm-lock.yaml ./ | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --prod | ||
``` | ||
|
||
Next up is installing our dependencies via `pnpm`. | ||
|
||
First, we enable [`corepack`](https://nodejs.org/api/corepack.html) in the Node `base` image. Corepack allows us to use `pnpm` right out of the box without having to install it ourselves. It's a nice convenience but watch out that you have the expected `pnpm` version. | ||
|
||
We then create our working directory, `app`, and copy in just our `package.json` and `pnpm-lock.yaml` files. Note that we do not copy in our entire codebase. We only care about installing our production dependencies at this stage. This is a best practice that allows us to take advantage of Docker's layer caching. | ||
|
||
Finally, we get to installing our packages via `pnpm`. This is broken into two `RUN` statements that are making use of [BuildKit cache mounts](/blog/how-to-use-buildkit-cache-mounts-in-ci). | ||
|
||
1. `pnpm fetch --frozen-lockfile` is a [pnpm feature](https://pnpm.io/cli/fetch) that is designed to improve building a Docker image. It fetches packages from the `pnpm-lock.yaml` file and stores them in the `pnpm` virtual store. Ignoring the `package.json` manifest. It's a nice optimization that avoids having to reinstall all packages in the `package.json` when a change occurs there that isn't related to the actual dependencies. | ||
|
||
2. `pnpm install --frozen-lockfile --prod` is the actual installation of our dependencies. We use the `--frozen-lockfile` flag to ensure that we are installing the exact same versions of our dependencies that are in our `package.json`. We also use the `--prod` flag to ensure that we are not installing any `devDependencies`. | ||
|
||
This is a best practice that allows us to take advantage of Docker's layer caching. | ||
|
||
We use the `--frozen-lockfile` flag to ensure that we are installing the exact same versions of our dependencies that we have installed locally. We also use the `--prod` flag to ensure that we are only installing our production dependencies. This is a best practice that allows us to take advantage of Docker's layer caching. | ||
|
||
#### Stage 3: `FROM base AS build` | ||
|
||
```dockerfile | ||
FROM base AS build | ||
|
||
RUN corepack enable | ||
WORKDIR /app | ||
COPY package.json pnpm-lock.yaml ./ | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile | ||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile | ||
COPY . . | ||
RUN pnpm build | ||
``` | ||
|
||
It's time to build our application. We start by enabling `corepack` again so that we have `pnpm` in this stage. We then configure our working directory to be `app` and copy in the `package.json` and `pnpm-lock.yaml` files as we saw in the `deps` stage. | ||
|
||
We then run `pnpm fetch --frozen-lockfile` and `pnpm install --frozen-lockfile` again. However, we omit the `--prod` flag as we want to install _all dependencies_ as we may need some dev ones to build our final application. If that wasn't the case, we could copy in our dependencies from our earlier `deps` stage. | ||
|
||
Once our dependencies have been installed into this stage, we then copy in our source code via the `COPY` statement. We then run our build command, `pnpm build`. | ||
|
||
#### Stage 4: `FROM base` | ||
|
||
```dockerfile | ||
FROM base | ||
|
||
WORKDIR /app | ||
COPY --from=deps /app/node_modules /app/node_modules | ||
COPY --from=build /app/dist /app/dist | ||
ENV NODE_ENV production | ||
CMD ["node", "./dist/index.js"] | ||
``` | ||
|
||
Our final stage is copying all of the files from our earlier stages into our final image. We start by setting our working directory to `app`, then have several `COPY` statements: | ||
|
||
1. We copy in our `node_modules` from our `deps` stage | ||
2. We then copy in the `dist` directory from our `build` stage containing the outputs from our `pnpm build` | ||
|
||
Finally, we set our `NODE_ENV` to production for any dependencies that have an optimized production mode. We then set our `CMD` to run our application. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
title: Best practice Dockerfiles for Node.js | ||
ogTitle: Best practice Dockerfiles for Node.js | ||
description: A set of best practice Dockrfiles for building Docker images for Node | ||
--- | ||
|
||
# Best practice Dockerfiles for Node.js | ||
|
||
We've assembled some best practice Dockerfiles for building Docker images for Node.js using different package managers. These Dockerfiles are what we recommend when building Docker images for Node applications, but they are not the only way to do it, so your mileage may vary. | ||
|
||
- [Dockerfile for Node.js using `pnpm`](/docs/languages/node-pnpm-dockerfile) |