Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TSC not found in Docker build

When building an image that needs to be compiled from typescript, I get this error.

sh: 1: tsc: not found

The command '/bin/sh -c npm run tsc' returned a non-zero code: 127

Here is the relevant code:

docker-compose.yaml

version: '3.1'

services:
  nodeserver:
    build:
      context: .
      target: prod
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src
      - ./public:/app/public
      - ./templates:/app/templates

Dockerfile

FROM node:15.11.0 AS base
EXPOSE 3000
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./

RUN npm install --only=production && npm cache clean --force

##########################################################################################

FROM base AS dev

ENV NODE_ENV=development

RUN npm install --only=development

CMD npm run dev

##########################################################################################

FROM dev AS source

COPY dist dist
COPY templates templates
COPY public public

RUN npm run tsc

##########################################################################################

FROM base AS test

COPY --from=source /app/node_modules /app/node_modules
COPY --from=source /app/templates /app/templates
COPY --from=source /app/public /app/public
COPY --from=source /app/dist /app/dist

CMD npm run test

##########################################################################################

FROM test AS prod

CMD npm start

package.json

{
  "name": "nodeserver",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node ./dist/app.js",
    "deploy": "git add . && git commit -m Heroku && git push heroku main",
    "tsc": "tsc --outDir ./dist",
    "dev": "npm run ts-watch",
    "test": "npm run jest --runInBand",
    "ts-watch": "tsc-watch --project . --outDir ./dist --onSuccess \"nodemon ./dist/app.js\""
  },
  "jest": {
    "testEnvironment": "node"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/MiquelPiza/nodeserver.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/MiquelPiza/nodeserver/issues"
  },
  "homepage": "https://github.com/MiquelPiza/nodeserver#readme",
  "dependencies": {
    "@sendgrid/mail": "^7.4.2",
    "bcryptjs": "^2.4.3",
    "express": "^4.17.1",
    "handlebars": "^4.7.7",
    "jsonwebtoken": "^8.5.1",
    "lodash": "^4.17.20",
    "mongodb": "^3.6.4",
    "mongoose": "^5.11.19",
    "multer": "^1.4.2",
    "socket.io": "^4.0.0",
    "validator": "^13.5.2"
  },
  "devDependencies": {
    "@types/bcryptjs": "^2.4.2",
    "@types/express": "^4.17.11",
    "@types/jsonwebtoken": "^8.5.0",
    "@types/lodash": "^4.14.168",
    "@types/mongoose": "^5.10.3",
    "@types/multer": "^1.4.5",
    "@types/node": "^14.14.33",
    "@types/sendgrid": "^4.3.0",
    "@types/validator": "^13.1.3",
    "env-cmd": "^10.1.0",
    "jest": "^26.6.3",
    "nodemon": "^2.0.7",
    "supertest": "^6.1.3",
    "tsc-watch": "^4.2.9",
    "typescript": "^4.2.3"
  },
  "engines": {
    "node": "15.11.0"
  }
}

tsconfig.json

{
  "compilerOptions": {

    "target": "es5", 
    "module": "commonjs",
    "strict": true,
    "strictNullChecks": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
  },
  "include": ["src"]
}

This dockerfile works:

FROM node:15.11.0 AS build

WORKDIR /app
COPY package.json .
RUN npm install
ADD . .
RUN npm run tsc


FROM node:15.11.0
WORKDIR /app

COPY package.json .
RUN npm install --production

ADD public ./public
ADD templates ./templates
COPY --from=build /app/dist dist

EXPOSE 3000
CMD npm start

I'm using this dockerfile for reference, from a Docker course: https://github.com/BretFisher/docker-mastery-for-nodejs/blob/master/typescript/Dockerfile I don't see what I'm doing wrong, the source stage should have the dev dependencies, among them typescript, so it should be able to run tsc.

Any help appreciated. Thanks.

EDIT:

In addition to using npm ci instead of npm install, I had to copy tsconfig.json to the working directory (and copy src directory instead of dist, which is created by tsc) for tsc to work properly. This is the modified source stage in the Dockerfile:

FROM dev AS source

COPY src src
COPY templates templates
COPY public public
COPY tsconfig.json tsconfig.json

RUN npm run tsc
like image 751
mikepa88 Avatar asked Apr 21 '21 16:04

mikepa88


People also ask

Why am I getting TSC command not found when trying to build?

I am getting tsc: command not found when trying to run build. This is part of my package.json Tried to run npm install --only=dev && npm run build && npm prune --production Your build job failed because it was terminated. This often happens due to resource exhaustion.

How does Docker dockerfile build work?

docker build works by running each Dockerfile step in a container. At the end of each step, that container is committed to a new image. The first step's container is created from the image specified in FROM.

Why can’t I reproduce the problem locally with Docker?

Nobody could reproduce the problem locally with Docker: it only happened in the CI/CD pipeline. What is going on? We needed to debug the Docker build on the CI/CD server. docker build works by running each Dockerfile step in a container. At the end of each step, that container is committed to a new image.

Does Docker build support caching?

docker build supports caching. If the same build step had already run successfully in the past, then that step will be skipped (no container will be spawned), and the image produced last time will be selected. If a certain step does not hit the cache, then all subsequent steps won't hit the cache either.


2 Answers

EDIT 2:

This issue has been resolved. If your package-lock.json file is corrupted, you may be able to fix it with the utility fix-has-install-script.

Original Answer:

Use npm ci (or add package-lock.json to your .dockerignore file, or delete package-lock.json in your local environment before building). The why is answered here.

EDIT 1:

Here's what I believe is going on. Disclaimer, I'm not an expert on nodejs or npm -- in fact I'm something of a novice. And all of this is conjecture based on some experiments.

What's going wrong?

npm is not linking the binaries for the dev dependencies via sym links in node_modules/.bin because the package-lock.json file has gotten into a corrupted state where the (prod) dependencies are in lockfileVersion 2 format, and the dev dependencies are still in lockfileVersion 1 format.

Why is this happening?

Note: Making a bunch of assumptions here.

  1. Your local host using using npm 6, and the docker container is using npm 7. Because of this, the existing package-lock.json is in lockfileVersion: 1 which doesn't include a bin section for dependencies that have binaries. Version 2 does save the bin: section, which npm must use to determine what binaries to install/link.

  2. When you run the production dependency install (e.g. NODE_ENV=production npm install), using npm version 7, npm is upgrading the version of your package-lock.json to lockfileVersion: 2, part of this includes saving bin: sections for the dependencies that install binaries. Importantly, it updates only the production dependencies. Now the package-lock.json file is corrupted because it claims to be in version 2 format, but all the dev dependencies are either still in version 1 or at least don't have the bin: section correctly applied.

  3. When you now try to install your dev dependencies, npm sees that the package-lock.json is in lockfileVersion: 2, so it assumes that the dev dependencies have been upgraded as well (but they haven't been, or at least not correctly). It doesn't find the bin: sections because they don't exist and so it doesn't link the binaries to the node_modules/.bin/ directory.

You can perform a minimum reproduction of it using this Dockerfile:

FROM node:14 as npm6
WORKDIR /app
# Create a node project using npm 6 and install a dev dependency
# that contains a binary.
RUN npm init --yes && \
    npm install --save-dev typescript

FROM node:15 as npm7
COPY --from=npm6 /app/package*.json /app/
WORKDIR /app
# Install production dependencies, then all dependencies. This should
# link the binaries for typescript in (e.g. tsc) under node_modules/.bin.
RUN npm install -g [email protected] && \
    npm install --production && \
    npm install

# Causes error, tsc not found.
CMD ["npx", "-c", "tsc --version"]

I couldn't find an existing bug ticket so I created one here. Perhaps it will get fixed.

like image 54
kthompso Avatar answered Oct 21 '22 23:10

kthompso


It's probably a NODE_ENV environment variable problem.

ENV NODE_ENV=production

If you set this way, the dependencies in devDependencies will not be installed.

like image 11
eeve Avatar answered Oct 21 '22 21:10

eeve