How do I use pnpm instead of npm?

I’m trying to get my node backend app up and running. It uses pnpm as a package manager so fly.io will have to run pnpm build to build my app but I can see it’s running “npm install” which is wrong because I don’t use npm.

And since I also use turborepo I want to only build a certain app in the monorepo so I will have to run “pnpm build --filter backend”, which in turn runs “turbo run build --filter backend” because I have a build script in my package.json.

You can opt to write your own dockerfile and build the node app which ever way you want (yarn, deno bundle, bun compile, pnpm…)

Example: fly-hello-nodejs/Dockerfile at main · gregmsanderson/fly-hello-nodejs · GitHub

Ref: How to override node version - #2 by greg

1 Like

I was afraid I had to go that route. Since I don’t know about Docker I would like to be able to do this without Docker at all.

I imagine I could have an install, build and a start script in the fly.toml that fly uses to install build and start my application.

To anyone from fly.io looking here I consider this a feature request:

I would like to have flyctl understand that I’m running pnpm instead of npm/yarn. It can look if a pnpm-lock.yaml exists and in that install and use pnpm. I can see yarn is supported in the flyctl repo so I guess something similar could be done for pnpm.

Generally the way it works is that fly launch will create a Dockerfile for you. If it isn’t doing so, I would like to know more about your project (probably starting with your package.json).

Here’s the current logic: flyctl/node.go at 29f47717124e3917a3641a284857df1db6f0c611 · superfly/flyctl · GitHub

Here’s the template: flyctl/Dockerfile at 29f47717124e3917a3641a284857df1db6f0c611 · superfly/flyctl · GitHub

If somebody can specify what they would like the Dockerfile to look like, I can work backwards and make that happen.

1 Like

I guess, an if (fstat pnpm-lock.yaml) packager = "pnpm" is enough for OP’s usecase (not sure if pnpm version must also be captured…).

This is currently how it looks:

PS C:\proj> flyctl deploy
==> Verifying app config
cdc420527dde: Pull complete
1d5b93fa786f: Pull complete
8cc107d322a1: Pull complete
12aff8b49586: Pull complete
64ef0dff31f4: Pull complete
361bb62e1605: Pull complete
861eb70bfbd5: Pull complete
210536913458: Pull complete
f10dea63a38d: Pull complete
ca5345e0831e: Pull complete
9d85cbc21ad4: Pull complete
b44ddde83051: Pull complete
d380858d7954: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:8dfddd4d83fe7ed55d1d08cae67908dcc940a04f0724b4e9a8b44fedc3800c4e
Status: Downloaded newer image for heroku/buildpacks:20
20-cnb: Pulling from heroku/heroku
Digest: sha256:49ddea370926985c40d2ad0e0957063937ed32bfbebcad77651d86beaf3d2c47
Status: Image is up to date for heroku/heroku:20-cnb
===> DETECTING
2 of 3 buildpacks participating
heroku/nodejs-engine 0.8.14
heroku/nodejs-npm    0.5.2
===> ANALYZING
Previous image with name "registry.fly.io/fly-builder-black-dust-748:cache" not found
[Checking Node.js version]
Detected Node.js version range: >=18.0.0
Resolved Node.js version: 19.4.0

[Installing Node.js distribution]
Downloading Node.js 19.4.0
Extracting Node.js 19.4.0
Installing Node.js 19.4.0
[INFO] Installing toolbox
[INFO] - yj
[INFO] Using npm v9.2.0 from Node
[INFO] Installing node modules

• Packages in scope: backend, web
• Running build in 2 packages• Remote caching disabled
backend:build: cache miss, executing c44d2d6e1f854d76
web:build: cache miss, executing d66f75ff36527dcd
backend:build: ERROR: command finished with error: exec: "pnpm": executable file not found in $PATH

 Tasks:    0 successful, 1 total
Cached:    0 cached, 1 total
  Time:    123ms

exec: "pnpm": executable file not found in $PATH
 ERROR  run failed: command  exited (1)
ERROR: failed to build: exit status 1
Error failed to fetch an image or build from source: executing lifecycle: failed with status code: 51

It actually looks at the engines field of the package.json which is really good to see.

The problem is this line: [INFO] Using npm v9.2.0 from Node. It shouldn’t do that because I use pnpm, so that I guess is the first thing to resolve is to make it install pnpm and then run pnpm install in the root.

Then in the end of the output it looks like something knows that I’m using pnpm because something tries to run pnpm(exec: "pnpm":) but of course fails because pnpm is not installed.

I’m also surprised to see heroku in my generated fly.toml, I’ve never used that :slight_smile:

Here’s the code: flyctl/node.go at master · superfly/flyctl · GitHub

What’s going on here is that knowing that your application is a Node.js application isn’t enough for us to know how to build and launch your application. So we start by looking for a number of popular frameworks (Next, Nuxt, Remix). Failing that, we look for a “start” script in your package.json. Failing that, we give up and see if a Heroku buildpack might work for you.

If you are using a popular platform, or are following defacto standards (e.g., build and start are common names for scripts in package.json), we can build a Dockerfile for you. If you have unique needs, you will need to create a Dockerfile specific to your project.

1 Like

I tried my best to make a functioning Dockerfile but I can’t just get it to work and I don’t know why. I’ve very little experience in both Docker and Linux environments so I appreciate pointers how I should move forward.

I got this:

FROM node:18 AS builder
RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -
WORKDIR /app
COPY . .
RUN pnpm exec turbo prune --scope=backend --docker

FROM node:18 AS installer
WORKDIR /app
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm fetch --prod
RUN pnpm install -r --offline --prod
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
RUN pnpm build --filter=backend

FROM node:18 AS runner
WORKDIR /app
COPY --from=installer /app .
EXPOSE 8080
CMD node apps/backend/dist/index.js

I’m getting this:

PS C:\project> flyctl deploy
==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-black-dust-748 ready
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.12 linux x86_64
[+] Building 17.8s (0/1)
[+] Building 11.7s (9/18)
 => [internal] load remote build context                                                                                                                  0.0s
 => copy /context /                                                                                                                                       6.4s
 => [internal] load metadata for docker.io/library/node:18                                                                                                0.6s
 => [installer  1/10] FROM docker.io/library/node:18@sha256:d871edd5b68105ebcbfcde3fe8c79d24cbdbb30430d9bd6251c57c56c7bd7646                              0.0s
 => CACHED [installer  2/10] WORKDIR /app                                                                                                                 0.0s
 => CACHED [builder 2/5] RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -                                                        0.0s
 => CACHED [builder 3/5] WORKDIR /app                                                                                                                     0.0s
 => CACHED [builder 4/5] COPY . .                                                                                                                         0.0s
 => ERROR [builder 5/5] RUN pnpm exec turbo prune --scope=backend --docker                                                                                0.8s
------
 > [builder 5/5] RUN pnpm exec turbo prune --scope=backend --docker:
#10 0.427 /bin/sh: 1: pnpm: not found
------
Error failed to fetch an image or build from source: error building: executor failed running [/bin/sh -c pnpm exec turbo prune --scope=backend --docker]: exit code: 127

I’ve done my best trying different strategies from other people having similar problems but none of them work. I always get stuck on this step with pnpm.

Let’s first get it working, then let’s look at optimizing (if necessary).

Delete all FROM statements except the first.
Delete all COPY statements except the first.
Delete all WORKDIR statements except the first.

Does the resulting Dockerfile work?

It’s still the same error on this line:
RUN pnpm exec turbo prune --scope=backend --docker

# https://turbo.build/repo/docs/handbook/deploying-with-docker
# https://pnpm.io/cli/fetch#usage-scenario

FROM node:18 AS builder
RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -
WORKDIR /app
COPY . .
RUN pnpm exec turbo prune --scope=backend --docker

#FROM node:18 AS installer
#WORKDIR /app
#COPY .gitignore .gitignore
#COPY --from=builder /app/out/json/ .
#COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm fetch --prod
RUN pnpm install -r --offline --prod
#COPY --from=builder /app/out/full/ .
#COPY turbo.json turbo.json
RUN pnpm build --filter=backend

#FROM node:18 AS runner
#WORKDIR /app
#COPY --from=installer /app .
EXPOSE 8080
CMD node apps/backend/dist/index.js
PS C:\project> flyctl deploy
==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-black-dust-748 ready
==> Creating build context
 => copy /context /                                                                                                                                       6.6s 
 => [internal] load metadata for docker.io/library/node:18                                                                                                0.7s 
 => [1/8] FROM docker.io/library/node:18@sha256:d871edd5b68105ebcbfcde3fe8c79d24cbdbb30430d9bd6251c57c56c7bd7646                                          0.0s 
 => CACHED [2/8] RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -                                                                0.0s 
 => CACHED [3/8] WORKDIR /app                                                                                                                             0.0s 
 => [4/8] COPY . .                                                                                                                                        5.0s 
 => ERROR [5/8] RUN pnpm exec turbo prune --scope=backend --docker                                                                                        0.5s 
------
 > [5/8] RUN pnpm exec turbo prune --scope=backend --docker:
#8 0.476 /bin/sh: 1: pnpm: not found
------
Error failed to fetch an image or build from source: error building: executor failed running [/bin/sh -c pnpm exec turbo prune --scope=backend --docker]: exit code: 127

OK, I tried running the above line on a fly machine, and got the following output:

==> Extracting pnpm binaries 7.26.0
Copying pnpm CLI from /tmp/tmp.7KnWyIbCGQ/pnpm to /root/.local/share/pnpm/pnpm
Appended new lines to /root/.bashrc

Next configuration changes were made:
export PNPM_HOME="/root/.local/share/pnpm"
export PATH="$PNPM_HOME:$PATH"

To start using pnpm, run:
source /root/.bashrc

This would suggest that you would get further if you added the following immediately after the RUN curl line:

ENV PNPM_HOME="/root/.local/share/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
1 Like

Thanks, it helped. I re-renabled the COPY commands, removed the --from argument from them and tried to run this again and now I got further but it still errors out in a line. It doesn’t seem like the path /app/out/json/ exists, but I believe it should be created by the RUN turbo prune command.

Because I use turborepo I’ve tried to follow this guide here:

PS C:\project> flyctl deploy
==> Verifying app config
--> Verified app config
==> Building image
[+] Building 10.8s (18/18) FINISHED
 => [internal] load remote build context                                                                                                                  0.0s 
 => copy /context /                                                                                                                                       6.2s 
 => [internal] load metadata for docker.io/library/node:18                                                                                                0.7s
 => [ 1/15] FROM docker.io/library/node:18@sha256:d871edd5b68105ebcbfcde3fe8c79d24cbdbb30430d9bd6251c57c56c7bd7646                                        0.0s
 => CACHED [ 2/15] RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -                                                              0.0s
 => CACHED [ 3/15] RUN pnpm add turbo -g                                                                                                                  0.0s
 => CACHED [ 4/15] WORKDIR /app                                                                                                                           0.0s
 => CACHED [ 5/15] COPY . .                                                                                                                               0.0s
 => CACHED [ 6/15] RUN turbo prune --scope=backend --docker                                                                                               0.0s
 => CACHED [ 7/15] COPY .gitignore .gitignore                                                                                                             0.0s
 => ERROR [ 8/15] COPY /app/out/json/ .                                                                                                                   0.0s
 => ERROR [ 9/15] COPY /app/out/pnpm-lock.yaml ./pnpm-lock.yaml                                                                                           0.0s
 => CACHED [10/15] RUN pnpm fetch --prod                                                                                                                  0.0s
 => CACHED [11/15] RUN pnpm install -r --offline --prod                                                                                                   0.0s
 => ERROR [12/15] COPY /app/out/full/ .                                                                                                                   0.0s
 => CACHED [13/15] COPY turbo.json turbo.json                                                                                                             0.0s
 => CACHED [14/15] RUN pnpm build --filter=backend                                                                                                        0.0s
 => ERROR [15/15] COPY /app .                                                                                                                             0.0s 
------
 > [ 8/15] COPY /app/out/json/ .:
------
------
 > [ 9/15] COPY /app/out/pnpm-lock.yaml ./pnpm-lock.yaml:
------
------
 > [12/15] COPY /app/out/full/ .:
------
------
 > [15/15] COPY /app .:
------
Error failed to fetch an image or build from source: error building: failed to compute cache key: "/app" not found: not found
FROM node:18 AS builder
RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -
ENV PNPM_HOME="/root/.local/share/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN pnpm add turbo -g
WORKDIR /app
COPY . .
RUN turbo prune --scope=backend --docker

# FROM node:18 AS installer
# WORKDIR /app
COPY .gitignore .gitignore
# COPY --from=builder /app/out/json/ .
# COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY /app/out/json/ .
COPY /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm fetch --prod
RUN pnpm install -r --offline --prod
# COPY --from=builder /app/out/full/ .
COPY /app/out/full/ .
COPY turbo.json turbo.json
RUN pnpm build --filter=backend

# FROM node:18 AS runner
# WORKDIR /app
# COPY --from=installer /app .
COPY /app .
EXPOSE 8080
CMD node apps/backend/dist/index.js

Without --from it is going to try to copy files from your development machine, and it isn’t finding /app there.

You already have: COPY . . which will copy your current working directory (recursively) to the WORKDIR of the docker images, so you really don’t need any other COPY statements until or unless you put back in the FROM lines.

1 Like

I managed to build the docker image now by removing more COPY lines. I’m a bit confused as to why they are needed. I’m unsure if I’m loosing out on something now when I’ve removed many lines compared to the beginning.

Anyway, in the end it asked me if I wanted to proceed with the deploy and said it was highly experimental. I said “y” and then it told me it failed to update the VM.

What may I be doing wrong here?

$ flyctl deploy --local-only --now
==> Verifying app config
--> Verified app config
==> Building image
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.22 linux x86_64
[+] Building 4.2s (0/1)
[+] Building 3.6s (13/13) FINISHED
 => [internal] load remote build context                                                                                           0.0s
 => copy /context /                                                                                                                1.6s
 => [internal] load metadata for docker.io/library/node:18                                                                         0.5s
 => [1/9] FROM docker.io/library/node:18@sha256:d871edd5b68105ebcbfcde3fe8c79d24cbdbb30430d9bd6251c57c56c7bd7646                   0.0s
 => CACHED [2/9] RUN curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -                                         0.0s
 => CACHED [3/9] RUN pnpm add turbo -g                                                                                             0.0s
 => CACHED [4/9] WORKDIR /app                                                                                                      0.0s
 => CACHED [5/9] COPY . .                                                                                                          0.0s
 => CACHED [6/9] RUN turbo prune --scope=backend --docker                                                                          0.0s
 => CACHED [7/9] RUN pnpm fetch --prod                                                                                             0.0s
 => CACHED [8/9] RUN pnpm install -r --offline --prod                                                                              0.0s
 => CACHED [9/9] RUN pnpm build --filter=backend                                                                                   0.0s
 => exporting to image                                                                                                             0.0s
 => => exporting layers                                                                                                            0.0s
 => => writing image sha256:602498be3a1270d55796d663d2e23bb57cc99a79836a9a6e6af9cff1017c7164                                       0.0s
 => => naming to registry.fly.io/fly-builder-black-dust-748:deployment-01GQW9SGND5MD3EGVVE509J1XG                                  0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/fly-builder-black-dust-748]
7eef64847dde: Layer already exists
35591e3f5c6b: Layer already exists
be9ba448d2c7: Layer already exists
25bbffacba3c: Layer already exists
44978bdc1261: Layer already exists
a44f1a9762be: Layer already exists
ba8fdeccda9a: Layer already exists
65102da7e940: Layer already exists
94a84c106f35: Layer already exists
42a6dd9a3516: Layer already exists
40f10f114091: Layer already exists
c32c7f13784e: Layer already exists
a4db1a405763: Layer already exists
9f4f964da727: Layer already exists
49b333f7bad4: Layer already exists
a463dbda4664: Layer already exists
a9099c3159f5: Layer already exists
deployment-01GQW9SGND5MD3EGVVE509J1XG: digest: sha256:6bcfdcaf9eeaebbe7be17db9d73877ce0b4690060f275bdba59d63e8f0601598 size: 3899
--> Pushing image done
image: registry.fly.io/fly-builder-black-dust-748:deployment-01GQW9SGND5MD3EGVVE509J1XG
? This feature is highly experimental and may produce unexpected results. Proceed? Yes
Deploying with rolling strategy ✓ tal and may produce unexpected results. Proceed? (y/N) y
Error failed to update VM 06e82ddd1bd987: Deploying over the remote builder is not allowed.

If Machines (VMs) were created with flyctl m run ... then flyctl deploy ... won’t work (why?). Instead exec flyctl m update --image registry.fly.io/<image>:<tag> ... (ref, docs) once per VM.

I have no recollection of ever calling flyctl m run on my machine but as I understand it the problem it that I’m using a new api called Machines? The why? post didn’t help me understand the problem I’m afraid :slight_smile: but I guess I have to update my app to somehow be complient with the new Apps v2?

So I ran the flyctl m update --image registry.fly.io/<image>:<tag> command, switched out registry.fly.io/<image>:<tag> to the image given to me in fly status which was flyio/rchab:sha-272d6db.

This was the command output of fly status btw:

$ fly status
App
  Name     = fly-builder-black-dust-748
  Owner    = personal
  Hostname = fly-builder-black-dust-748.fly.dev
  Platform = machines

ID              STATE   REGION  HEALTH CHECKS   IMAGE                   CREATED                 UPDATED
06e82ddd1bd987  started fra                     flyio/rchab:sha-272d6db 2023-01-24T20:26:42Z    2023-01-28T16:44:43Z

Then I ran flyctl m update --image flyio/rchab:sha-272d6db but it said I missed a machine ID. I guess the ID is the one given to me in the fly status command output which was 06e82ddd1bd987.

So then I ran: flyctl m update 06e82ddd1bd987 --image flyio/rchab:sha-272d6db and it gave me:

$ flyctl m update 06e82ddd1bd987 --image flyio/rchab:sha-272d6db
Searching for image 'flyio/rchab:sha-272d6db' remotely...
image found: img_lrjxpgwe3j7v7n6q
Image: registry-1.docker.io/flyio/rchab:sha-272d6db
Image size: 99 MB

Configuration changes to be applied to machine: 06e82ddd1bd987 (floral-waterfall-2882)

        ... // 11 identical lines
                        "tty": false
                },
-               "image": "flyio/rchab:sha-272d6db",
-               "metadata": null,
+               "image": "registry-1.docker.io/flyio/rchab:sha-272d6db",
+               "metadata": {},
                "mounts": [
                        {
        ... // 17 identical lines
? Apply changes? Yes
Updating machine 06e82ddd1bd987
No health checks found
Machine 06e82ddd1bd987 updated successfully!

So now I guess my app is updated and fully compliant with Apps v2/Machines API. My thinking is that I can just run fly deploy and then it will work but it still gives me the error:

Error failed to update VM 06e82ddd1bd987: Deploying over the remote builder is not allowed.

Pretty sure this is a Fly builder image (see), and not the one you built with flyctl deploy --local-only --now.

There are quite a few ways to update a Machine:

# build and deploy in one step
flyctl m update <vmid> --dockerfile </path/to/Dockerfile> <other-args>

# build and push docker image to the Fly registry
flyctl deploy --dockerfile </path/to/Dockerfile> --local-only --push --auto-confirm
# or, specify a unique image-label
flyctl deploy --dockerfile </path/to/Dockerfile> --local-only --push --auto-confirm --image-label <some-per-app-uniq-id>

# note down image:tag from the output of the deploy cmd above
# image:tag looks something like registry.fly.io/<app-name>:<image-label>
# image-label is either deployment-<base32> or whatever was specified at build-time
# now exec 
flyctl m update <vmid> --image registry.fly.io/<image:tag> <other-args>

Do ref these docs, if you haven’t already.


No matter what you do, VMs started with flyctl m run cannot be (as of today) deployed with flyctl deploy (is my understanding).

Oh I don’t need a fly.toml file I realised since that’s for the old version 1. Alright now I’ve got my app built, deployed and I can see it’s running so I think this thread is resolved.
Thanks for all the help.