Matrix-bot-sdk on docker node:alpine

I have an issue with docker image size which is hard to resolve. Running a 146M package, it takes a 1.34GB container (matrix-bot) for the image to work properly.

REPOSITORY                              TAG               IMAGE ID       CREATED          SIZE
translator-bot-zh                       v1.0              156d9b3ba89c   4 minutes ago    385MB
matrix-bot                              latest            9939ff6eb899   11 hours ago     1.34GB

Though I can build a node:alpine image that is 385MB, I keep running into dependency and other issues.

sudo docker run --platform linux/x86_64 --rm -it translator-bot-zh:v1.0
5f75e9cc0768:/app# npm start

> bot@1.0.0 start
> node dist/basic-bot.js

node:internal/modules/cjs/loader:1472
  return process.dlopen(module, path.toNamespacedPath(filename));
                 ^

Error: Error relocating /app/node_modules/matrix-bot-sdk/node_modules/@matrix-org/matrix-sdk-crypto-nodejs/matrix-sdk-crypto.linux-x64-musl.node: __register_atfork: symbol not found
    at Module._extensions..node (node:internal/modules/cjs/loader:1472:18)
    at Module.load (node:internal/modules/cjs/loader:1206:32)
    at Module._load (node:internal/modules/cjs/loader:1022:12)
    at Module.require (node:internal/modules/cjs/loader:1234:19)
    at require (node:internal/modules/helpers:176:18)
    at Object.<anonymous> (/app/node_modules/matrix-bot-sdk/node_modules/@matrix-org/matrix-sdk-crypto-nodejs/index.js:175:31)
    at Module._compile (node:internal/modules/cjs/loader:1375:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1434:10)
    at Module.load (node:internal/modules/cjs/loader:1206:32)
    at Module._load (node:internal/modules/cjs/loader:1022:12) {
  code: 'ERR_DLOPEN_FAILED'
}

Node.js v21.5.0
sudo docker build -t translator-bot-zh:alpine .
[+] Building 564.2s (14/14) FINISHED                                                                                               docker:default

 => ERROR [10/10] RUN npm start                                                                                                              7.4s
------
 > [10/10] RUN npm start:
3.471 
3.471 > bot@1.0.0 start
3.471 > node dist/basic-bot.js
3.471 
6.832 node:internal/modules/cjs/loader:1473
6.832   return process.dlopen(module, path.toNamespacedPath(filename));
6.832                  ^
6.832 
6.832 Error: Error relocating /app/node_modules/matrix-bot-sdk/node_modules/@matrix-org/matrix-sdk-crypto-nodejs/matrix-sdk-crypto.linux-x64-musl.node: __register_atfork: symbol not found
6.832     at Module._extensions..node (node:internal/modules/cjs/loader:1473:18)
6.832     at Module.load (node:internal/modules/cjs/loader:1207:32)
6.832     at Module._load (node:internal/modules/cjs/loader:1023:12)
6.832     at Module.require (node:internal/modules/cjs/loader:1235:19)
6.832     at require (node:internal/modules/helpers:176:18)
6.832     at Object.<anonymous> (/app/node_modules/matrix-bot-sdk/node_modules/@matrix-org/matrix-sdk-crypto-nodejs/index.js:175:31)
6.832     at Module._compile (node:internal/modules/cjs/loader:1376:14)
6.832     at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
6.832     at Module.load (node:internal/modules/cjs/loader:1207:32)
6.832     at Module._load (node:internal/modules/cjs/loader:1023:12) {
6.832   code: 'ERR_DLOPEN_FAILED'
6.832 }
6.834 
6.834 Node.js v20.11.0
------
Dockerfile:26
--------------------
  24 |     
  25 |     # Run Appp
  26 | >>> RUN npm start
  27 |     
--------------------
ERROR: failed to solve: process "/bin/sh -c npm start" did not complete successfully: exit code: 1

Question

How do we get matrix-bot-SDK running on an alpine container without these errors?

Dockerfile

# Build stage
FROM node:20-alpine

# Install bash and other libraries
RUN apk --update --no-cache add bash
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache musl
RUN apk add gcompat

# Set bash as the default shell
CMD ["/bin/bash"]

# Set the working directory inside the container
WORKDIR /app

# Copy only the package.json and package-lock.json to the container
COPY . . 

# Install dependencies and remove unnecessary files in a single layer
RUN npm install && npm cache clean --force

# Build App
RUN npm run build

# Run Appp
RUN npm start

package.json:

{
  "name": "bot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "build": "npx tsc",
    "start": "node dist/basic-bot.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "Sati",
  "license": "ISC",
  "compilerOptions": {
    "skipLibCheck": true
  },
  "dependencies": {
    "@google-cloud/translate": "latest",
    "@matrix-org/matrix-sdk-crypto-nodejs": "latest",
    "axios": "latest",
    "dotenv": "latest",
    "esm": "latest",
    "franc": "latest",
    "htmlencode": "latest",
    "lru-cache": "latest",
    "matrix-bot-sdk": "latest",
    "matrix-js-sdk": "latest",
    "uuid": "latest",
    "typescript": "latest",
    "vorpal": "latest",
    "@types/express": "latest",
    "another-json": "latest",
    "async-lock": "latest",
    "chalk": "latest",
    "express": "latest",
    "glob-to-regexp": "latest",
    "hash.js": "latest",
    "html-to-text": "latest",
    "lowdb": "latest",
    "mkdirp": "latest",
    "morgan": "latest",
    "postgres": "latest",
    "request": "latest",
    "request-promise": "latest",
    "sanitize-html": "latest"
},
  "devDependencies": {
    "@types/google-translate-api": "latest",
    "@babel/core": "latest",
    "@babel/eslint-parser": "latest",
    "@babel/eslint-plugin": "latest",
    "@testcontainers/postgresql": "latest",
    "@types/async-lock": "latest",
    "@types/jest": "latest",
    "@types/lowdb": "latest",
    "ts-mocha": "latest",
    "@types/node": "latest",
    "@types/sanitize-html": "latest",
    "@types/simple-mock": "latest",
    "@typescript-eslint/eslint-plugin": "latest",
    "@typescript-eslint/parser": "latest",
    "better-docs": "latest",
    "eslint": "latest",
    "eslint-config-google": "latest",
    "eslint-plugin-import": "latest",
    "eslint-plugin-matrix-org": "latest",
    "get-port": "latest",
    "jest": "latest",
    "jsdoc": "latest",
    "matrix-mock-request": "latest",
    "simple-mock": "latest",
    "taffydb": "latest",
    "testcontainers": "latest",
    "tmp": "latest",
    "ts-jest": "latest",
    "typescript": "^5.3.3"
  }
}

Searching for error codes plus distro names can almost always give you some clues.

Search term: ERR_DLOPEN_FAILED alpine

Alpine is a special distro and uses different libraries (musl libc). If the application doesn’t support it, you can’T run it on Alpine.

If yuou can build the app in a debian based distro for example, choose the “slim” variant. It will only help if the size difference was caused by the base image and not the dependencies.

You can aso use multistage builds and optimize the image layers.