First deploy - Docker fails on npm run build saying wrong Esbuild

Towards the end of the image building process, it fails on npm run build with this long Esbuild error (I only copied part of it).

ERROR [web client 6/6] RUN npm run build
CANCELED [build 4/5] COPY . .
[client 6/6] RUN npm run build:
my-app-frontend@0.0.0 build
ng build
❯ Building...
✖ Building... [FAILED:
You installed esbuild for another platform than the one you're currently using.
This won't work because esbuild is written with native code and needs to
install a platform-specific binary executable.

Specifically the "@esbuild/darwin-x64" package is present but this platform
needs the "@esbuild/linux-x64" package instead. People often get into this
situation by installing esbuild on Windows or macOS and copying "node_modules"
into a Docker image that runs Linux, or by copying "node_modules" between
Windows and WSL environments.

If you are installing with npm, you can try not copying the "node_modules"
directory when you copy the files over, and running "npm ci" or "npm install"
on the destination platform after the copy. Or you could consider using yarn
instead of npm which has built-in support for installing a package on multiple
platforms simultaneously.
...

There are 7 more paragraphs of this with all the same information. I do not have Esbuild in my package.json though it is in my package-lock.json as a dependency. I even tried including a bit of code that deletes node_modules though it should be unnecessary. My node_modules folder is in docker.ignore. How do I move past this error and deploy?

Can you tell me a bit more about your app?

In particular, if I run rails new esbuild-demo --js esbuild I get a package.json that looks like:

{
  "name": "app",
  "private": true,
  "devDependencies": {
    "esbuild": "^0.25.2"
  },
  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets"
  },
  "dependencies": {
    "@hotwired/stimulus": "^3.2.2",
    "@hotwired/turbo-rails": "^8.0.13"
  }
}

Note that esbuild is present.

Also make sure that your .dockerignore includes

/node_modules/

That directory is both operating system specific and huge, you don’t want to upload it to the build server.

esbuild is not present at all in my package.json and my .dockerignore file does include:

/node_modules/

Without either seeing your application or you sharing more, I don’t think anybody here can help you.

You’ve indicated that you have a Rails application. Apparently something you depend on depends on esbuild. As near as I can tell, that something isn’t Rails itself even though Rails does have the ability to make use of esbuild.

Perhaps share your package.json?

{
  "name": "my-app-frontend",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "serve:ssr:my_app_frontend": "node dist/my_app_frontend/server/server.mjs"
  },
  "private": true,
  "dependencies": {
    "@angular/cdk": "^19.2.7",
    "@angular/common": "^19.2.0",
    "@angular/compiler": "^19.2.0",
    "@angular/core": "^19.2.0",
    "@angular/forms": "^19.2.0",
    "@angular/material": "^19.2.7",
    "@angular/platform-browser": "^19.2.0",
    "@angular/platform-browser-dynamic": "^19.2.0",
    "@angular/platform-server": "^19.2.0",
    "@angular/router": "^19.2.0",
    "@angular/ssr": "^19.2.3",
    "@fortawesome/angular-fontawesome": "^1.0.0",
    "@fortawesome/fontawesome-svg-core": "^6.7.2",
    "@fortawesome/free-brands-svg-icons": "^6.7.2",
    "@fortawesome/free-regular-svg-icons": "^6.7.2",
    "@fortawesome/free-solid-svg-icons": "^6.7.2",
    "bootstrap": "^5.3.3",
    "express": "^4.18.2",
    "postcss": "^8.5.3",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.15.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^19.2.3",
    "@angular/cli": "^19.2.3",
    "@angular/compiler-cli": "^19.2.0",
    "@types/express": "^4.17.17",
    "@types/jasmine": "~5.1.0",
    "@types/node": "^18.18.0",
    "jasmine-core": "~5.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "source-map-explorer": "^2.5.3",
    "typescript": "~5.7.2"
  }
}

Here is my dockerfile:

# syntax=docker/dockerfile:1
# check=error=true

# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t demo .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name demo demo

# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.2
ARG NODE_VERSION=22.14.0

FROM node:$NODE_VERSION-slim AS client

WORKDIR /rails/my_app_frontend

ENV NODE_ENV=production

# Install node modules
COPY my_app_frontend/package.json my_app_frontend/package-lock.json ./
RUN npm install

# build client application
COPY my_app_frontend .
RUN npm run build


FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-jemalloc-slim AS base

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

# Update gems and bundler
RUN gem update --system --no-document && \
    gem install -N bundler

# Install base packages
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libvips postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment
ENV BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development:test" \
    RAILS_ENV="production"


# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential libffi-dev libpq-dev libyaml-dev && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y imagemagick libvips && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Copy built client
COPY --from=client /rails/my_app_frontend/build /rails/public

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
    chown -R 1000:1000 db log storage tmp
USER 1000:1000

# Entrypoint sets up the container.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/rake", "litestream:run", "./bin/thrust", "./bin/rails", "server"]

OK, so you have a frontend that you are building and copying into the Rails public directory.

When I install using your package.json, I see the following in the lockfile:

    "node_modules/@esbuild/darwin-x64": {
      "version": "0.25.1",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
      "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      } 
    },  

Note optional true and os darwin. Such a package shouldn’t be installed an a debian linux slim image. Check to see that this matches what you are seeing.

You can also try removing my_app_frontend/package-lock.json from

COPY my_app_frontend/package.json my_app_frontend/package-lock.json ./

Generally the preference is to install using the exact versions you built with locally, but I sense that isn’t so important in this case.

Damn, I really thought that was the perfect solution but I am getting the exact same error.

In an empty directory, I placed the contents of your package.json, as well as the following Dockerfile:

ARG NODE_VERSION=22.14.0
FROM node:$NODE_VERSION-slim AS client

WORKDIR /rails/my_app_frontend

ENV NODE_ENV=production

# Install node modules
COPY package.json ./
RUN npm install

I first ran docker build . locally. Then fly launch. In both cases the build succeeded.