Show HN: Smallest Node.js Docker images
github.com
github.com
It could be either via a hole in that executable, or just by using the executable to greatly simplify the penetration and / or privilege escalation.
That said, now I have "good" broadband, I pay a lot less attention to the image size.
I've always been fond of "premature optimisation is the root of all evil" [1], but still... wasting resources makes me feel bad. I feel like the guy that buys 10 plastic bottles of 0.5l water instead of 2x2.5 litres.
The guys building ( owning ) Slack, cryptocurrencies, e-commerce websites are on the other end though. I hardly can count a day when my MacBook's fan doesn't spin like crazy, because I'm just visiting an HTML page to read the text.
In other words. Yeah ... you can save some precious time to optimise that, because you don't want to be the person that starts the fans of colleagues' Macs ( having a docker image with your app running on my Mac ). Energy is what you are saving and that's priceless in today's polluted world.
On several projects the management and sales team were at odds with the dev team about app performance. They wanted it faster and the dev team was full of premature optimization cargo cult members. By cult member, I mean people who say that phrase to get out of thinking.
The line between right and fast is blurry. There’s a big chunk of refactorings that improve readability and performance and if you work with those you get better at your job, avoid the cult members and please the business.
Now that I’ve typed that last paragraph I want to go through Refactoring and categorize the ones that fall under “right and fast”...
A much better solution is to fix the image format to better handle the usecases that it needs to handle. (This is what I'm working on at the moment.)
In the "builder" you only need package.json and package-lock.json files to install dependencies. The rest of the sources can be copied in the "scratch-node" image. This would make caching work till the last line where only code changes are included. Code is modified much more frequently than dependencies.
You sample can look like this:
FROM node as builder
WORKDIR /app
COPY package.json package-lock.json index.js ./
RUN npm install --prod
FROM astefanutti/scratch-node
COPY --from=builder /app/node_modules /node_modules
COPY ./ ./
ENTRYPOINT ["./node", "index.js"]
Go ahead, go crazy and add all of the dev dependencies you need to build your package. Once you've done that, take the built package and put it into another container that has only the runtime dependencies.
The ideal use-case for this is compiling Go, since you end up with a 1GB build container and a 12MB single-binary production container if you compile with static linking. Just beware when going the FROM SCRATCH route that you get nothing to go with it, you can't shell into the container or run "ps" or "lsof" for debugging because none of those exist.
Your image processes run on the host anyway, so just `ps` or `lsof` from the host. I've never had to exec into a Go/scratch container.
You can’t do this if you don’t have access to and root privileges on the host.
Sure, multistage builds are useful, but you could always get the same features (and more) by making packages for whatever distribution you use and installing it in your container. I get that's it's not as easy as writing a shell script in your Dockerfile to build all your dependencies, but sometimes better solutions aren't free.
(Also this is one of the reasons why the layer model of deduplication is flawed, and why I'm working on improving it. You shouldn't have to care how large your logical image size is.)
I was once in the camp of small Docker images, but realized it's simply not worth the tradeoff, since there's only one upside to them, and that upside is fast transfer of images.
However, that argument becomes pointless when using a proper CI/CD stack. As a developer, you don't normally upload images yourself, but push changes to GitHub, then Jenkins/Travis/whatever takes over, builds the image, and pushes it into production/staging/whatever. Since CD tool of choice is usually also on the cloud, we don't have to worry about image size, nor to any of the CD vendors charge for data transfer.
I'd rather have bigger images (I base mine off Debian now, used to be Alpine) and not have to worry with lack of ported tools and libraries, than vice-versa.
No big deal. :)
It doesn't really matter if a container contains a 5 year old imagemagick binary if that binary is never used by anything. It's the equivalent of a bug in unreachable code.
Security (and privacy) are largely about minimizing surface area.
Your argument doesn't make any sense, how would "the exploit" make an unreachable vulnerability reachable without being able to execute the vulnerable code in the first place?
Please don't say "using a different vulnerability that allows us to execute arbitrary code".
Such flaws have not even always required a direct connection - years ago someone found a flaw in common USENET software that let them execute command lines via specially crafted newsgroup posts, and effectively get a really slow (store and forward via multiple servers slow) interactive shell.
Their ability to exploit it was directly dependent on what else was reachable from a shell. Run it in a chroot without binaries, and they could do quite little. Run it somewhere the attacker had access to tools and they suddenly had a shell behind your firewall.
The increased risk from more binaries is not hypothetical, but something many of us have experienced the difference of first hand.
I've personally reviewed more than one set of logs from intrusion attempts where the attackers had found a way to execute commands but were unable to do harm because they were fumbling around looking for ways to penetrate further but didn't find any of the tools they needed.
Sorry, but I'm going there anyway. Imagine two different exploits. One is a remote code execution exploit and the other is a privilege escalation exploit.
Let's say your application has an exploit an attacker then manages to obtain a reverse shell (imagemagick, xml parsing, etc all have had multiple such exploits over the years). If you're running things correctly, that reverse shell is not privileged. It's the apache user or something. Not a good situation to be in, they can do a lot of damage, but at least some things are safe. They don't have root.
Now the attacker finds that you have X11 installed. It's an old version that was installed by default. It happens to have a root privilege escalation exploit via fonts. Now the attacker has root.
That's what I mean by surface area. Thinking in terms of "have we been compromised" isn't sufficient. Being able to contain the attack is important, and dead code lying around factors into how well you can contain it.
We’ve used the Node.js containers in production and investigated the other languages, but never deployed them. We did have some issues with devops not being able to log into the running containers, but always found a solution that I believe in the end was a better long term pattern for ops.
Definitely a cute experiment, but probably of limited real-world use. I wonder what the smallest _practical_ node container would look like?
It's certainly only practical when every byte matters. At that point it might also make sense to prebundle the dependencies and copy that over.
It'd be interesting to see whether this becomes relevant if serverless-like constraints suddenly apply to a Docker cloud service
I suspect this could be relevant many place, keeping images small also hardens security.
It's something certain developers I've encountered seem to ignore, even when creating something that might handle health or financial information.
Did you have to build your docker images from scratch, or did the security audit folks certify upstream images? What about updates?
Our security folks haven't really decided what to do with containers although some people are just using RHEL7 base images since its "enterprise-y". Our group personally uses alpine base images. If we have something like a java service hosted by Tomcat, we build alpine then build tomcat and then build our "service" container. While most people are fine pulling from Dockerhub, we do work in closed-loop environments and have a private docker registry where we host our "chain" of docker images which are versioned and updated regularly.
Works great for us :)
(not looking to argue, if there's another reason to keep these tools around, please let me know, I'm always looking to learn more)