Add caching to Node Dockerfile

8 months ago

I want to ask for feedback on my Dockerfile. This is the first time I've set up caching.

This Dockerfile works. My first deploy of this file was 143 secs, the second 124 secs. But that's still really slow IMO for that second try. Have I set this up properly for Node and PNPM as package manager?

Is there anything else I can do to speed these builds up?

I also noticed it's required to hardcode your session-id. I've now bound this to the service-id of my main production environment in Railway. What are the implication of this when a different environment is created in Railway for your project, for example when you create a new PR on Github?

Thanks!

# Use Node.js as the base image
FROM node:20-alpine

# Install pnpm
RUN npm install -g pnpm

# Set working directory
WORKDIR /app

# Install dotenvx
RUN curl -sfS https://dotenvx.sh/install.sh | sh

# Copy package.json and pnpm-lock.yaml
COPY package.json pnpm-lock.yaml ./

# Install dependencies with caching
RUN --mount=type=cache,id=s/e87cdb12-ca40-472a-b18b-fc6f19c34f3f-/root/.local/share/pnpm/store,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile

# Copy the rest of the application code
COPY . .

# Specify the environment variables needed at build time
ARG DOTENV_PRIVATE_KEY_PRODUCTION

# Build the application with caching
RUN --mount=type=cache,id=s/e87cdb12-ca40-472a-b18b-fc6f19c34f3f-/root/.cache/pnpm,target=/root/.cache/pnpm \
    pnpm run build

# Expose the port the app runs on
EXPOSE 3000

# Start the application
CMD ["pnpm", "start"]

0 Replies

8 months ago

what takes the most time to run?


8 months ago

I believe you'd shared a tool somewhere with which I could extract build logs. Would that be helpful?


8 months ago

Building takes ~80 secs


8 months ago

Here I filtered the logs for done:

#12 80.01   ✔ done

#12 DONE 80.7s

#13 exporting layers 15.2s done

#13 exporting manifest sha256:7a3c7f748177c383ddcb646668c4feda059f1169a216e894fd1002ea0f51934c 0.3s done

#13 exporting config sha256:7b82807699583bf53ac0eb6662ad7c2becd54cbb7e53ac67cbd19706e62f3a35 done

#13 exporting attestation manifest sha256:6dcc37b278a65409922832067c6f3c92afec59e903491135fa55874c085501ea done

#13 exporting manifest list sha256:4faeed252081451d247c1a960c7e55a06ccba801bac34cdf1dd2f093d8f353e5 done

#14 DONE 0.0s

#13 pushing layers 15.7s done

#13 pushing manifest for us-west1.registry.rlwy.net/e87cdb12-ca40-472a-b18b-fc6f19c34f3f:b7081d8b-891b-480a-a771-b6c31bd57b36@sha256:4faeed252081451d247c1a960c7e55a06ccba801bac34cdf1dd2f093d8f353e5 1.3s done

#13 DONE 32.5s

8 months ago

so it's the building, exporting and pushing of layers, and pushing the manifest which take the most time.


8 months ago


8 months ago

I can't seem to get the bookmarklet to work with my browser. Is there another way I can export this for you?


8 months ago

Can't get the bookmark to work with Arc browser and injecting the JS via console doesn't work either.


8 months ago

ctrl / cmd + k -> logs



8 months ago

Does that work?



8 months ago

since its not the full logs, nope doesnt work


8 months ago

okay that one works


8 months ago

Sweet 👏🏼


8 months ago

do you happen to know how big this image is?


8 months ago

How can I find out?


8 months ago

by building locally


8 months ago

Once I do that, where can I find the image? Does it get created in my project folder?


8 months ago

Sorry I'm new to Docker.


8 months ago

How do I build the Docker file locally using Railway's cache? Do I have to create a new Dockerfile to build locally without the caching statements?


8 months ago

you dont build it with railway's cache, nor is that something you can do, you simply build the image, you shouldnt need to change anything


8 months ago

OK I will try.


8 months ago

I had to make a bunch of changes to the Dockerfile to get it working locally, but got the local creation of the image done. The size of the image is 1.16GB. Here's the updated Dockerfile:

# Build stage
FROM node:20-alpine AS builder

# Install pnpm
RUN npm install -g pnpm

# Set working directory
WORKDIR /app

# Install dotenvx
RUN curl -sfS https://dotenvx.sh/install.sh | sh

# Copy package.json and pnpm-lock.yaml
COPY package.json pnpm-lock.yaml ./

# Install dependencies with caching
RUN --mount=type=cache,id=s/e87cdb12-ca40-472a-b18b-fc6f19c34f3f-/root/.local/share/pnpm/store,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile

# Copy the rest of the application code
COPY . .

# Specify the environment variables needed at build time
ARG DOTENV_PRIVATE_KEY_PRODUCTION

# Set Node options for increased memory
ENV NODE_OPTIONS="--max-old-space-size=4096"

# Build the application with caching
RUN --mount=type=cache,id=s/e87cdb12-ca40-472a-b18b-fc6f19c34f3f-/root/.cache/pnpm,target=/root/.cache/pnpm \
    NODE_ENV=production pnpm run build

# Production stage
FROM node:20-alpine

# Install pnpm in the production stage
RUN npm install -g pnpm

# Set working directory
WORKDIR /app

# Copy the build output and package.json from the builder stage
COPY --from=builder /app/build ./build
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml

# Install only production dependencies
RUN pnpm install --prod

# Expose the port the app runs on
EXPOSE 3000

# Start the application
CMD ["pnpm", "start"]

What do you think of the new Dockerfile?

And are there any ways you think I can speed up builds?


8 months ago

<#727685388893945877> 5) don't ping team or conductors


8 months ago

1.16gb is still big but totally doable imo


8 months ago

Oh sorry! I'll remove the tag.


8 months ago

Done 🙂


8 months ago

Do you think there are any further optimisations I could make?


8 months ago

🤔 I don't think so, I never had to optimize the size of my images


8 months ago

so I can't really say


8 months ago

you can definitely get it smaller than 1.16GB.

it's also possible your mount targets are incorrect, aka not the actual path pnpm stores the cache at, so nothing actually gets cached


8 months ago

How can I reduce the size of the image?


8 months ago

And how would I know where to find the correct mount targets?


8 months ago

I referenced this https://github.com/railwayapp/nixpacks/blob/main/src/providers/node/mod.rs thinking this was the right example for node and found pnpm cache mentioned as such: const PNPM_CACHE_DIR: &str = "/root/.local/share/pnpm/store/v3"


8 months ago

Not sure if I derived my mounts correctly from that.


8 months ago

these articles (part 1 & 2) were pretty good walkthrough on how to reduce image sizes https://lengrand.fr/reducing-dockers-image-size-while-creating-an-offline-version-of-carbon-now-sh/


8 months ago

mine went from ~900MB to ~350MB after optimization


8 months ago

Nice I incorporated some of the tips.

Does the Slim package work with Railway too? Asking since it seems CLI driven so not sure if you could incorporate that into the build file.


8 months ago

yes no issues with the -slim node image variants


8 months ago

How would I incorporate that into the creation of my image? Can I run docker-slim in the Dockerfile somehow?


8 months ago

Any hints or example repos I could take a look at, perhaps?


8 months ago

oh sorry i thought you wanted to use slim images, no railway does not support running docker slim


8 months ago

I thought so -- thanks for clarifying.


8 months ago

I was about to halve the size of the image though by pruning the node and pnpm modules in the build phase, and then explicitly copying the node_modules in the production step instead of re-installing the dependencies there.


8 months ago

🎉


8 months ago

Any feedback on this? How do I know if I'm using the right mount targets?


8 months ago

im sure you arent the first to do this on the vast internet, and it wouldn't be specific to railway


8 months ago

Hint taken. I will do more research.


8 months ago

Also I had this remaining question:

I also noticed it's required to hardcode your session-id. I've now bound this to the session-id of my main production environment in Railway. What are the implication of this when a different environment is created in Railway for your project, for example when you create a new PR on Github?


8 months ago

you mean service id


8 months ago

and in that case, it will fail


8 months ago

But so far, when I've been testing these builds and put my new code in a new PR, a new Railway environment was started which deployed just fine with the Dockerfile that had my service-id included.


8 months ago

I don't understand the limitations of this properly. Is the idea that I hardcode the service-id of each Railway production environment that I'll be deploying the image in?


8 months ago

again, service id, not session id


8 months ago

Sorry yes, typo.


8 months ago

But my question still stands.


8 months ago

yes you do need to hardcore that service id for every service you deploy to


8 months ago

I see. Understood.