The final image is 1GB, and the intermediate “build” image is 4GB. It seems the intermediate “build” image has roughly 2 copies of the node_modules dependencies in src/node_modules (meteor npm clean-install), and bundle/programs/server/npm/node_modules (cd bundle/programs/server && meteor npm install), and the .meteor folder is gigantic (~1.6GB of packages and 400MB of package metadata). Is there anything one can do about this?
Is there a way to avoid installing dev dependencies that are not required for building (mocha, puppeteer, etc.)?
Creating your own docker images for your deployment experience is a great gain on your setup, offering reusability and environment replication in a single time investment.
From the details provided, it seems you’ve customized the zodern/meteor image to include your app’s source code, build processes, dev dependencies and caches (e.g .meteor/local). This approach can result in larger image sizes due to bundling everything together. I recommend the following:
In my experience, ensuring lightweight Docker images involves splitting them into two categories: development and production.
Development image: Contains your git repository, dev dependencies, scripts, QA, testing, and populated caches. It aims to facilitate fast verification processes and app runs, especially useful for CI or staging environments. This image may be relatively large.
Production image: Includes only compiled code and necessary dependencies for running the app in production. A good example is the zodern/meteor. Its primary goal is to remain lightweight.
Once you have these images, utilize Docker’s multi-stage images feature to build your production image. This approach involves preparing the build in a heavier environment and then copying the artifacts into the smallest possible image for running the app. In the first stage, use the development image to build the app bundle from source code, and in the second stage, copy the bundle into the lightweight production environment.
Here’s a illustration of the approach I use in my projects:
This approach ensures the final image remains lightweight without unnecessary development artifacts. Additionally, consider further optimizations such as running the app server node_module preparation (cd /built_app/programs/server && npm install) and applying post-cleaning steps like modclean or node-prune during the development stage, then copying the cleaned artifacts into the production image.
@nachocodoner I build everything from source with docker build. .dockerignore contains .meteor/local.
@nachocodoner I have implemented a two-stage build process: first install meteor, install dependencies with meteor npm install, build from sources, run meteor npm install inside the bundle and fetch the necessary node binary, then copy the bundle and the node binary to a new image, configure $PATH, and conclude with CMD ['node', 'dist/main.js'].
@nachocodoner I can try something like that, but I will definitely not rely on those abandoned packacges directly.
@superfail , the big point here is to use different images and container to each step of the build/deploy process. So, in the end, I’m using a small image of node to run my app. No Meteor installed there.
@raragao I am going to try to find a different base image for the final image. Meteor claims that the only dependency of the bundle is the Node binary with the version which matches meteor node --version. Is this completely true?
I think so. But I didn’t understand why you are trying to find a different base image to final image. That one is the official NODE image and has the smallest size. FROM node:14.21.3-alpine
I realize that this app depends on sharp, canvas, pdfjs, and @mui which together already take half of the layer space. So maybe illusory to think one could improve things further.