Skip to content

Conversation

@CaseConsole
Copy link

Shrink the docker image by 210MB, by using esbuild to bundle the code into single file (the executables have duplicates now, need to switch the local modules to ESM to be able to use them and reduce this, but due to duplication the resulting build is only 5MB larger than current).

Result:

REPOSITORY                  TAG            IMAGE ID       CREATED             SIZE
remnawave                   esbuild        57f62663002c   7 minutes ago       466MB
remnawave/backend           latest         ca07ab5a9a70   2 days ago          676MB

The bundled code results in:

22.2M   dist
4.0K    docker-entrypoint.sh
4.0K    ecosystem.config.js
4.0K    esbuild.config.mjs
86.3M   frontend
4.0K    frontend-crowdin
1.1M    libs
101.2M  node_modules
432.0K  package-lock.json
8.0K    package.json
24.0K   prisma
docker run --rm -ti --entrypoint /bin/sh remnawave/backend:latest -c 'du -sh *'
16.9M   dist
4.0K    docker-entrypoint.sh
4.0K    ecosystem.config.js
86.3M   frontend
0       frontend-crowdin
1004.0K libs
426.5M  node_modules
416.0K  package-lock.json
8.0K    package.json
280.0K  prisma

The savings mostly come from reduced node_modules, by not including the code that is not executed; and avoiding shipping of entire node_modules, but keeping the minimals that do not bundle:

/opt/app/node_modules # ls
@bull-board        @prisma            @remnawave         class-transformer  jiti               lodash             prisma             redis-info         zod

Also removing of duplicated prisma client libs that are not relevant:

/opt/app # find node_modules -name "*so.node"
node_modules/.prisma/client/libquery_engine-debian-openssl-3.0.x.so.node
node_modules/.prisma/client/libquery_engine-linux-musl-arm64-openssl-3.0.x.so.node
node_modules/.prisma/client/libquery_engine-linux-musl-openssl-3.0.x.so.node
node_modules/@prisma/engines/libquery_engine-linux-musl-arm64-openssl-3.0.x.so.node
node_modules/@prisma/engines/libquery_engine-linux-musl-openssl-3.0.x.so.node
node_modules/prisma/libquery_engine-debian-openssl-3.0.x.so.node
node_modules/prisma/libquery_engine-linux-musl-arm64-openssl-3.0.x.so.node
node_modules/prisma/libquery_engine-linux-musl-openssl-3.0.x.so.node

One of these is a symlink:

find node_modules -name "*so.node"
node_modules/@prisma/engines/libquery_engine-linux-musl-openssl-3.0.x.so.node
node_modules/.prisma/client/libquery_engine-linux-musl-openssl-3.0.x.so.node

Hosts view doesn't work, but this is probably from dev branch:

[API Server: #0] 2025-08-18 17:46:26.268   ERROR [HostsService] 	{"code":"P2022","meta":{"modelName":"Hosts","column":"hosts.vless_route_id"},"clientVersion":"6.10.1","name":"PrismaClientKnownRequestError"} - { stack: [ null ] }
[API Server: #0] 2025-08-18 17:46:26.269   ERROR [HttpExceptionFilter] 	Get all hosts error - { stack: [ null ], code: 'A050', path: '/api/hosts/' }

@snyk-io
Copy link

snyk-io bot commented Aug 18, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Summary

This PR implements a significant Docker image optimization that reduces the image size from 676MB to 466MB (30% reduction) by introducing esbuild bundling. The changes implement a sophisticated multi-stage build process that bundles the Node.js application code into single files and dramatically reduces the node_modules directory from 426MB to 101MB.

The optimization works through several key mechanisms:

  1. Code Bundling: A new esbuild.config.mjs configuration bundles multiple entry points (main API server, CLI, processors, and scheduler) into single executable files, leveraging tree-shaking to eliminate unused code

  2. Minimal Production Dependencies: Creates a streamlined production node_modules containing only essential runtime dependencies that cannot be bundled (@bull-board/ui, @prisma/client, class-transformer, class-validator, prisma, zod)

  3. Prisma Optimization: Implements architecture-specific cleanup of Prisma engine binaries to remove unused platform-specific shared libraries

  4. Multi-stage Docker Build: Modifies the Dockerfile to leverage the bundling process while maintaining the same runtime environment

The changes also include a minor code cleanup in render-templates.service.ts that removes a duplicate 'SINGBOX' case from a switch statement, keeping the version that dynamically parses client versions rather than using a hardcoded version.

This optimization strategy is particularly effective for Node.js applications with heavy dependency trees where only a fraction of the installed packages are actually used at runtime. The approach maintains the existing application architecture while significantly improving deployment efficiency through reduced image size.

Confidence score: 2/5

  • This PR has significant potential for runtime failures due to the complexity of bundling a NestJS application with decorators, dynamic imports, and Prisma integration
  • Score reflects high risk from bundling breaking NestJS's reflection-based dependency injection, potential Prisma client generation issues, and complex build process dependencies
  • Pay close attention to the Dockerfile changes, esbuild configuration, and package.json dependency modifications as these could cause deployment failures

4 files reviewed, 2 comments

Edit Code Review Bot Settings | Greptile

'@nestjs/sequelize/dist/common/sequelize.utils',
'@nestjs/typeorm/dist/common/typeorm.utils',
'@nestjs/websockets/socket-module',
'@remnawave/*',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: external pattern '@remnawave/*' may not work correctly with current local library structure - consider being more specific

@kastov
Copy link
Contributor

kastov commented Aug 18, 2025

Not sure about bundling all things into one .js file... nestjs/nest#1706 (comment)

@CaseConsole
Copy link
Author

Yes, the mongoose example is handled within esbuild.config.mjs.

Backend is starting up and working fine; node as well; it is a significant image size reduction that doesn't need any maintenance with all of the logic being contained in a single file.

I think things have changed since 2020, lots of references to bundling:

  1. https://docs.nestjs.com/cli/monorepo#webpack-options
  2. https://blog.logrocket.com/getting-started-with-nestjs-vite-esbuild/
  3. https://www.npmjs.com/package/@rnw-community/nestjs-webpack-swc
  4. esbuild support nestjs/nest-cli#731
  5. https://www.webnuz.com/article/2022-09-13/NestJS%20%20esbuild%20workarounds

@kastov
Copy link
Contributor

kastov commented Aug 18, 2025

What is compressed size?

@CaseConsole
Copy link
Author

61MB / 29% smaller:

docker save remnawave:esbuild | gzip > esbuild.tar.gz
docker save remnawave/backend:latest | gzip > backend.tar.gz

du -sh *.tar.gz
211M    backend.tar.gz
150M    esbuild.tar.gz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants