NoVPS
FeaturesBlogSupport
Join waitlist
Tutorials

Microcontainers — Lightweight, Portable Docker Containers

Travis Reeder

Wed, Aug 28, 2024

Main picture

Docker allows you to package your application along with all its dependencies into a single, self-contained image. This image can then be used to run your application in containers. However, the downside is that these images often include more than what's necessary, leading to unnecessarily large images and containers. Many Docker users start by using the official repositories for their preferred programming language, but these can result in bloated images—more like the size of a skyscraper when you could be aiming for something closer to a birdhouse. The extra bulk isn't needed.

For example, if you build a Node.js image using the official Node image, the minimum size is around 643 MB. I created a simple "Hello World" Node app using this official image, and the resulting image was 644 MB.

That's enormous! My app is less than 1 MB with its dependencies, and the Node.js runtime itself is only around 20 MB. So what's taking up the remaining 620 MB? Surely, we can find a way to streamline this.

What is a Microcontainer?

Comparison

A Microcontainer includes only the essential OS libraries, language dependencies, and the application itself—nothing extra.

Instead of beginning with an overly large setup, you start with the bare essentials and add dependencies only as they are needed.

For example, by using a minimal base image and installing just the necessary components like Node.js and its dependencies for the same Node app mentioned earlier, the resulting image is just 29MB—a reduction in size by 22 times!

You can test this yourself by running both versions to see the difference. First, run:

docker run --rm -p 8080:8080 treeder/tiny-node:fat

Then, run:

docker run --rm -p 8080:8080 treeder/tiny-node:latest

These commands will launch the exact same Node app, but the sizes of the images are vastly different.

Why Are Microcontainers Great?

Microcontainers offer several significant advantages:

  1. Size — Microcontainers are extremely small. As demonstrated earlier, without altering any code, you can reduce an image’s size by 22 times compared to a standard image.

  2. Fast/Easy Distribution — Their small size means images download much faster from a Docker registry (like Docker Hub), allowing for quicker distribution across different machines.

  3. Improved Security — With fewer programs and less code in the container, there’s a smaller attack surface. Additionally, the base OS can be more secure (more on this later).

These benefits mirror those of Unikernels but without the associated drawbacks.

How to Build Microcontainers

The base for all Docker images is the scratch image, which is essentially empty. While this may seem limiting, it’s ideal for creating the smallest possible image for your application if you can compile it to a static binary with zero dependencies, as you can with Go or C. For example, my treeder/static-go image, which includes a Go web app, is just 5MB in total.

That’s about as small as it gets—just the scratch image plus your application binary.

Not everyone uses Go, so you may have more dependencies and need something more substantial than the scratch image. This is where Alpine Linux comes in. Alpine Linux is a security-focused, lightweight Linux distribution based on musl libc and busybox. While I won’t dive into the technical details, the most relevant feature for this discussion is its “lightweight” nature. The base Alpine image is only 5MB.

Now, we have a minimal OS base with a reliable package system to add only the necessary dependencies. For our simple Node app, we need only Node.js, so we add just the Node package to our Dockerfile, which looks like this:

FROM alpine RUN apk add --no-cache nodejs

Simple and clean. The image now contains only Node.js and its essentials.

To add our code to the image, we need just a few more lines in our Dockerfile:

COPY . /app WORKDIR /app CMD ["node", "app.js"]

You can find sample code and full build instructions here, but the key takeaway is that we now have a very small OS + only the dependencies we need + our application code—nothing more.

This approach works for all programming languages.

Base Images for All Languages

Luckily, we’ve already built optimized base images for all major languages, available here. These images are regularly updated and optimized for size, making them a slightly better choice than building from scratch. Using the Iron.io base images, the Dockerfile for the Node app mentioned earlier changes to this:

FROM iron/node COPY . /app WORKDIR /app CMD ["node", "app.js"]

For each language, there are two image versions: one for building and one for running. The build images contain all the necessary build tools and are generally much larger than the runtime images.

For example, to build your Node dependencies, you’d use iron/node:dev like this:

docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app iron/node:dev npm install

Then, use iron/node in your Dockerfile or to run it:

docker run --rm -p 8080:8080 iron/node

This process is similar for other languages, using their respective build/vendor/run commands.

If you need a different language version, simply change the tag, like iron/node:4.1 or iron/node:0.12. All version tags for each language are available on Docker Hub, such as Node’s tags.

How to Build and Package for All Languages

We also provide examples for using these base images with most major languages here. Each README walks you through building dependencies, testing code, creating a small Docker image, and testing the image.

No Going Back

After following this guide, you should be able to create Docker images that contain only what is needed to run your app. Once you start launching containers using these images, you’ll have entered the world of Microcontainers—and you won’t want to go back.


"Just used your ‘tiny image’ technique on one of my Go repos. It’s awesome! Thanks for the great post. Shrunk it down to 5MB—amazing."Harlow Ward @ Clearbit

Subscribe to stay updated

Never miss out on valuable insights and tips. Subscribe to our newsletter and get the latest blog posts delivered straight to your inbox.

Legal

Privacy PolicyTerms and ConditionsAcceptable Use Policy
NoVPS

© 2024 NoVPS Cloud LTD

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.