Developers don’t just use pNPM because they hate NPM (that’s what Yarn is for). They use it because node_modules are the heaviest objects in the universe. But there are other things to love about PNpm besides its ability to shave gigabytes off your hard disk and saving you a considerable amount of time wasted mucking around with node_modules. Here I’ll show you how to use pnpm fetch to increase the speed of your Docker builds using an --offline install.
Setup
Start by creating a new SvelteKit project. If you already have one that’s great. Go ahead and create a new one anyway so you don’t face any integration issues while you’re trying this out. And make sure to use pnPM to install packages so you can appreciate an alternative universe with more disk space:
npm init svelte@next my-app
cd my-app
pnpm install
Upon installation, choose the Skeleton project when prompted and accept the defaults for the remaining prompts then install. You should see output like:
Expand to view output
Packages: +36
++++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /home/vhs/.pnpm-store/v3
Virtual store is at: node_modules/.pnpm
Progress: resolved 53, reused 36, downloaded 0, added 36, done
node_modules/.pnpm/esbuild@0.13.15/node_modules/esbuild: Running postinstall script, done in 142ms
devDependencies:
- @sveltejs/adapter-auto 1.0.0-next.14
- @sveltejs/kit 1.0.0-next.240
- svelte 3.46.2
Appreciate the fact you no longer have look at NPM nag messages during package installation. Those npm fund messages sure are annoying aren’t they?
Dockerizing
Now that you’ve got a functional SvelteKit app go ahead and take a quick break. Make a sandwich. Or review your bandwhich. When you’re finished go ahead and create a Dockerfile with the contents adapted from this GPL-licensed work:
FROM node:lts-alpine
WORKDIR /tmp
RUN apk add gpg gpg-agent && \
wget -qO - https://keybase.io/pnpm/pgp_keys.asc | gpg --import && \
wget -q https://get.pnpm.io/SHASUMS256.txt && \
wget -q https://get.pnpm.io/SHASUMS256.txt.sig && \
gpg --verify SHASUMS256.txt.sig SHASUMS256.txt && \
wget https://get.pnpm.io/v6.16.js && \
grep v6.16.js SHASUMS256.txt | sha256sum -c - && \
cat v6.16.js | node - add --global pnpm && \
rm SHASUMS256.txt v6.16.js
WORKDIR /app
COPY pnpm-lock.yaml ./
RUN pnpm fetch --dev
ADD . ./
RUN pnpm install --offline --dev
RUN pnpm run build
EXPOSE 3000
CMD [ "pnpm", "preview", "--", "--port", "3000", "--host" ]
Before building your project with Docker create a .dockerignore file by copying the .gitignore file set-up when the project was initialized:
cp .gitignore .dockerignore && cat .dockerignore
Then run the build:
docker build .
You should see output like:
Expand to view output
Sending build context to Docker daemon 37.28MB
Step 1/11 : FROM node:lts-alpine
lts-alpine: Pulling from library/node
Digest: sha256:2f50f4a428f8b5280817c9d4d896dbee03f072e93f4e0c70b90cc84bd1fcfe0d
Status: Downloaded newer image for node:lts-alpine
---> 23990429c0d7
Step 2/11 : WORKDIR /tmp
---> Using cache
---> f01ee7083b80
Step 3/11 : RUN apk add gpg gpg-agent && wget -qO - https://keybase.io/pnpm/pgp_keys.asc | gpg --import && wget -q https://get.pnpm.io/SHASUMS256.txt && wget -q https://get.pnpm.io/SHASUMS256.txt.sig && gpg --verify SHASUMS256.txt.sig SHASUMS256.txt && wget https://get.pnpm.io/v6.16.js && grep v6.16.js SHASUMS256.txt | sha256sum -c - && cat v6.16.js | node - add --global pnpm && rm SHASUMS256.txt v6.16.js
---> Using cache
---> a5beabfbacf8
Step 4/11 : WORKDIR /app
---> Using cache
---> 3e6c487870ee
Step 5/11 : COPY pnpm-lock.yaml ./
---> b0fc928965cf
Step 6/11 : RUN pnpm fetch --dev
---> Running in ac9c03235f09
Importing packages to virtual store
Already up-to-date
Progress: resolved 1, reused 0, downloaded 0, added 0
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /root/.pnpm-store/v3
Virtual store is at: node_modules/.pnpm
Progress: resolved 35, reused 0, downloaded 25, added 25
Progress: resolved 35, reused 0, downloaded 34, added 34
.../esbuild@0.13.15/node_modules/esbuild postinstall$ node install.js
.../esbuild@0.13.15/node_modules/esbuild postinstall: [esbuild] Failed to find package "esbuild-linux-64" on the file system
.../esbuild@0.13.15/node_modules/esbuild postinstall: This can happen if you use the "--no-optional" flag. The "optionalDependencies"
.../esbuild@0.13.15/node_modules/esbuild postinstall: package.json feature is used by esbuild to install the correct binary executable
.../esbuild@0.13.15/node_modules/esbuild postinstall: for your current platform. This install script will now attempt to work around
.../esbuild@0.13.15/node_modules/esbuild postinstall: this. If that fails, you need to remove the "--no-optional" flag to use esbuild.
.../esbuild@0.13.15/node_modules/esbuild postinstall: [esbuild] Trying to install package "esbuild-linux-64" using npm
Progress: resolved 35, reused 0, downloaded 35, added 35, done
.../esbuild@0.13.15/node_modules/esbuild postinstall: Done
Removing intermediate container ac9c03235f09
---> a4575a6f1f2a
Step 7/11 : ADD . ./
---> cc0885311a75
Step 8/11 : RUN pnpm install --offline --dev
---> Running in fa507656cbb3
Recreating /app/node_modules
Lockfile is up-to-date, resolution step is skipped
Progress: resolved 1, reused 0, downloaded 0, added 0
Packages: +35
+++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /root/.pnpm-store/v3
Virtual store is at: node_modules/.pnpm
.../esbuild@0.13.15/node_modules/esbuild postinstall$ node install.js
.../esbuild@0.13.15/node_modules/esbuild postinstall: [esbuild] Failed to find package "esbuild-linux-64" on the file system
.../esbuild@0.13.15/node_modules/esbuild postinstall: This can happen if you use the "--no-optional" flag. The "optionalDependencies"
.../esbuild@0.13.15/node_modules/esbuild postinstall: package.json feature is used by esbuild to install the correct binary executable
.../esbuild@0.13.15/node_modules/esbuild postinstall: for your current platform. This install script will now attempt to work around
.../esbuild@0.13.15/node_modules/esbuild postinstall: this. If that fails, you need to remove the "--no-optional" flag to use esbuild.
.../esbuild@0.13.15/node_modules/esbuild postinstall: [esbuild] Trying to install package "esbuild-linux-64" using npm
Progress: resolved 35, reused 35, downloaded 0, added 35, done
.../esbuild@0.13.15/node_modules/esbuild postinstall: Done
devDependencies:
- @sveltejs/adapter-auto 1.0.0-next.14
- @sveltejs/kit 1.0.0-next.240
Removing intermediate container fa507656cbb3
---> bff3db5a6c7d
Step 9/11 : RUN pnpm run build
---> Running in 99a2e8ec599d
> my-app@0.0.1 build /app
> svelte-kit build
vite v2.7.13 building for production...
transforming...
✓ 13 modules transformed.
rendering chunks...
.svelte-kit/output/client/\_app/manifest.json 1.15 KiB
.svelte-kit/output/client/\_app/layout.svelte-7e82aae6.js 0.53 KiB / gzip: 0.35 KiB
.svelte-kit/output/client/\_app/error.svelte-2ccd6fb7.js 1.56 KiB / gzip: 0.75 KiB
.svelte-kit/output/client/\_app/pages/index.svelte-179fe8b9.js 0.80 KiB / gzip: 0.47 KiB
.svelte-kit/output/client/\_app/chunks/vendor-358e88b8.js 7.21 KiB / gzip: 2.96 KiB
.svelte-kit/output/client/\_app/start-825cfb21.js 21.59 KiB / gzip: 7.36 KiB
vite v2.7.13 building SSR bundle for production...
transforming...
✓ 11 modules transformed.
rendering chunks...
.svelte-kit/output/server/manifest.json 0.87 KiB
.svelte-kit/output/server/app.js 45.23 KiB
.svelte-kit/output/server/entries/pages/layout.svelte.js 0.24 KiB
.svelte-kit/output/server/entries/pages/error.svelte.js 0.72 KiB
.svelte-kit/output/server/entries/pages/index.svelte.js 0.32 KiB
.svelte-kit/output/server/chunks/index-211225cf.js 2.24 KiB
Run npm run preview to preview your production build locally.
> Using @sveltejs/adapter-auto
> Could not detect a supported production environment. See https://kit.svelte.dev/docs#adapters to learn how to configure your app to run on the platform of your choosing
> ✔ done
> Removing intermediate container 99a2e8ec599d
> ---> 9b188369f3c7
> Step 10/11 : EXPOSE 3000
> ---> Running in 157ed9627fac
> Removing intermediate container 157ed9627fac
> ---> 5082288f6c80
> Step 11/11 : CMD [ "pnpm", "preview", "--", "--port", "3000", "--host" ]
> ---> Running in d719b92d1c65
> Removing intermediate container d719b92d1c65
> ---> 903f31fdaa94
> Successfully built 903f31fdaa94
>
Notice the Digest in the build output. It’s the long sha256 digest. Add it to your Dockerfile as a best practice for Dockerizing node apps.
FROM node:lts-alpine@sha256:2f50f4a428f8b5280817c9d4d896dbee03f072e93f4e0c70b90cc84bd1fcfe0d
Finally, start the app using my favorite command docker run -dp:
docker run -dp 3000:3000 ba9fd746506d
Where ba9fd746506d is your IMAGE which you can get by piping to head:
docker images -q | head -1
Or do the docker run -dp and pipe to head at the same time:
docker run -dp 3000:3000 $(docker images -q | head -1)
That will start your app daemonized (in the background) so you can access your app from a web browser on port 3000. Verify it’s running with docker ps or lazydocker then view your app running in your favorite web browser.
Dockerize Early
While you can always Dockerize you app down the line I like to do mine before I even need to so I can git bisect to the location where it eventually breaks. As an added benefit, a Dockerized app will help you determine deficiencies in your build process earlier on before they come back to bite you.
For instance, if you were using NPM and not PnPM in this case I can can almost guarantee you your Docker builds are going to be slower than those using pNpm. How can I know that? Because every time there’s a change to the package manifest package.json and you’re using NPM Docker is going to download and reinstall every… single… dependency…
Whereas if you’re using Pnpm you can pnpm fetch all dependencies from a lockfile and, in a later docker build stage, perform an --offline installation. Then if your manifest changes but your dependencies don’t you won’t have to pay the resource penalty of reinstalling them. How about it?
Summary
In this post I showed you how to Dockerize a SvelteKit app using PNPM and explained the benefits of doing so. I also covered why you should containerize your application early in the application development lifecycle and left you with some insights as to how PNPM can not only save you time and space in development but in your CI/CD workflows as well. For more information on PNPM and SvelteKit please check out their websites. Both are very cool projects paving the way to a brighter future for the Web.