Join WhatsApp
Join Now
Join Telegram
Join Now

Building Minimal Docker Images on Linux: Using Distroless and Scratch

Avatar for Noman Mohammad

By Noman Mohammad

Published on:

Your rating ?

Bloated Docker Images? Let’s Fix That.

Ever stared at a Docker image that felt… huge? You’re not alone. We’ve all been there. It’s like dragging around a giant suitcase when all you need is a small backpack.

Here’s the scary part: Almost 80% of containers running in production have extra stuff in them. Stuff they don’t need. This isn’t just a waste of space. It’s a big problem for security and speed. That multi-gigabyte image? It’s a playground for hackers.

The Real Cost of Chunky Docker Images

Every extra megabyte in your Docker image comes with a price. A real one.

  • Slowdowns, anyone? Bigger images mean your build and deployment processes take longer. Your team waits. And waits. Those minutes add up fast.

  • Security holes. This is the big one. The National Vulnerability Database tells us that 60% of container attacks happen because of *unnecessary* packages left in images. Think about it: every extra tool or library you include is another door an attacker might try to open.

  • Compliance headaches. Rules like GDPR want you to keep things lean and secure. Those handy packages you left in “just in case”? They could become a legal problem.

Imagine this scenario:

You need to push a critical security fix. Pronto. But your deployments crawl. Why? Because your images are enormous. While you’re waiting, attackers are busy. They’re poking around for that forgotten curl command or a bash shell you didn’t even know was there. This isn’t just a theoretical problem; companies lose millions every year to these preventable attacks.

Your Mission: Build Tiny Containers. Here’s How.

Good news! You have two fantastic ways to shrink your Docker images: Scratch and Distroless. They both aim for minimalism, but in different ways. And they both deliver awesome results.

Using Scratch: The Ultimate Minimalist

Think of Scratch as an empty canvas. It’s the most minimal base image you can get. How minimal? It has absolutely nothing. No shell, no libraries, no file system. Nothing. This extreme purity makes it perfect for applications that compile into a single, self-contained file, like programs written in Go or Rust.

Let’s build a Go application with Scratch. First, we compile our Go app to be super self-contained:

# Compile a completely static binary. This means it has everything it needs built right in.
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

Then, your Dockerfile is incredibly simple:

FROM scratch
# We just copy our single app file into the image
COPY app /app
# And tell Docker to run it
CMD ["/app"]

Now, build and run your tiny image:

docker build -t my-minimal-app .
docker run my-minimal-app

The result? An image as small as 2MB! That’s often smaller than a simple PDF document.

Pro Tip: Debugging a Scratch image can be tricky because there’s no shell. For development, you can temporarily use busybox:glibc as your base image. It gives you basic tools without adding much size.

Leveraging Distroless: Minimalism with Just Enough Runtime

Distroless images, brought to us by Google, offer a slightly different, but equally powerful, approach. Instead of *nothing*, they give you *just enough*. They include only what your application needs to run (like a Python interpreter or a Java Virtual Machine), but skip all the extra stuff like shells, package managers, or common Linux utilities. It’s like having the engine without the car stereo or cup holders.

Let’s put a Python application into a Distroless image:

# Stage 1: Build all our Python dependencies. We use a full Python image for this.
FROM python:3.9-slim as builder
COPY requirements.txt .
RUN pip install --user -r requirements.txt

# Stage 2: Now, use the Distroless image. It only has Python, nothing else.
FROM gcr.io/distroless/python3
# We copy only the installed dependencies from our builder stage
COPY --from=builder /root/.local /root/.local
# And then our application code
COPY app.py .

ENV PYTHONPATH=/root/.local/lib/python3.9/site-packages
CMD ["app.py"]

What about Java applications? Here’s how it looks:

# Stage 1: Build our Java application with Maven.
FROM maven:3.8-jdk-11 as builder
COPY . .
RUN mvn package

# Stage 2: Switch to the Distroless Java image.
FROM gcr.io/distroless/java11-debian11
# Copy our packaged application from the builder
COPY --from=builder /target/app.jar .
CMD ["app.jar"]

Distroless offers specific images for different programming languages:

  • gcr.io/distroless/base: A super minimal base for anything that doesn’t fit another category.
  • gcr.io/distroless/python3: For your Python apps.
  • gcr.io/distroless/java11: If you’re running Java 11.
  • gcr.io/distroless/nodejs: For your Node.js projects.

Real-world insight: Google’s own container security team says Distroless images can cut down on known vulnerabilities (CVEs) by a whopping 90% compared to typical base images. That’s a huge win for security!

Making Things Secure: Best Practices

Both Scratch and Distroless are big steps for security. But they need a slightly different approach:

For Scratch images:

  • Always double-check that your application is fully static. Use ldd your_binary to make sure it doesn’t secretly rely on system libraries.
  • If your app talks over HTTPS, you’ll need to include CA certificates. Just copy them from a standard builder image.
  • Does your app need to know the local time? You might need to copy in timezone data too.

For Distroless images:

  • Debugging can still be tough without a shell. Google offers debug images (like gcr.io/distroless/python3/debug). These include a shell and some tools for troubleshooting. Just remember to use the regular image for production!
  • Always scan your final images. Tools like Trivy or Grype can find any remaining vulnerabilities.
  • Keep your base images updated. Google regularly pushes security patches, so pull the latest versions.

The National Institute of Standards and Technology (NIST) even recommends minimal container images as a core security practice. It’s that important!

Speed Test: Scratch vs. Distroless

How do these stack up in terms of performance?

Metric Scratch Distroless
Base Image Size 0MB (it’s empty!) 20-50MB (just the bare minimum runtime)
Shell Access ❌ Nope ❌ Nope
Runtime Dependencies ❌ None (all built-in) ✔️ Yes (Python, Java, Node.js, etc.)
Ideal For Super static apps (Go, Rust) Apps needing a runtime (Python, Java, Node.js)
Typical App Size 2-10MB 25-100MB

Here’s the takeaway: A smaller image deploys much faster. A 10MB image can deploy 60 times faster than a 600MB image. That’s the difference between waiting seconds or waiting minutes. Imagine that speed boost across all your deployments!

Your Action Plan: Get Started!

Ready to trim down those Docker images? Here’s your checklist:

  1. Look at your current images: Use docker image history. You might be shocked at what’s in there.
  2. Pick your path: Is your app static? Go with Scratch. Does it need a runtime like Python or Java? Distroless is your friend.
  3. Use multi-stage builds: This is key! It lets you build your app in a full environment, then copy only the final, small output into your minimal image.
  4. Don’t forget .dockerignore: Keep unnecessary files (like your node_modules during a build stage, or Git history) out of your image.
  5. Scan, scan, scan: Regularly run vulnerability scanners (like Trivy or Grype) on your finished images.
  6. Measure the difference: Track your deployment times. See how much faster things get.

Many teams see their image sizes shrink by 80-90% on their first try. Your security team will high-five you, your DevOps team will love the faster deployments, and your cloud bill might just get a little lighter.

Got Questions? I’ve Got Answers!

Q: Can I use Scratch with Python or Node.js?
A: Not directly. These languages need their runtime environments. That’s where Distroless comes in, giving you just enough runtime without all the extra baggage.

Q: How do I debug Distroless containers if there’s no shell?
A: Use the /debug versions of the Distroless images (e.g., gcr.io/distroless/python3/debug). They include a shell and some debugging tools for development. Just remember to switch back for production!

Q: Are Distroless images reliable?
A: Absolutely! Google maintains them, pushes regular security updates, and uses them widely in their own production systems. They’re definitely ready for prime time.

Q: How do I get CA certificates into a Scratch image?
A: You’ll copy them from a builder image. Something like: COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

Q: Can I install packages with apt-get or pip in a Distroless image?
A: Nope, and that’s the whole point! Distroless images *don’t include* package managers to reduce the attack surface. Install all your dependencies in an earlier build stage, then copy the *finished* application into your Distroless image.

Q: How often should I update my base images?
A: Aim for at least monthly. And definitely update immediately if you hear about any critical security issues affecting your specific base image.

Q: What about Windows containers?
A: These tips mainly apply to Linux containers. For Windows, look into Microsoft’s own minimal base images, like mcr.microsoft.com/windows/nanoserver.

By using Distroless and Scratch, you’re not just making your Docker images smaller. You’re making them *safer* and *faster*. Start simplifying your container strategy today. Your production environment will thank you for it!

Leave a Comment