The base image you choose for your Docker containers can make a dramatic difference in the final image size. Let's explore the options and learn when to use each one.
Base Image Comparison
Here's a size comparison of popular base images:
# Full Ubuntu-based image
FROM ubuntu:22.04 # ~77MB
FROM node:20 # ~1.1GB
# Alpine-based images
FROM alpine:3.18 # ~7MB
FROM node:20-alpine # ~180MB
# Slim variants
FROM node:20-slim # ~250MB
# Distroless
FROM gcr.io/distroless/nodejs20 # ~100MBStandard Images (Ubuntu, Debian)
Full-featured Linux distributions like Ubuntu and Debian come with:
- Complete package managers (apt, dpkg)
- Many pre-installed utilities and tools
- Full shell environments
- Large size (hundreds of megabytes to gigabytes)
Best for: Development environments, complex applications requiring many system tools, legacy applications.
Alpine Linux
Alpine is a security-oriented, lightweight Linux distribution perfect for containers:
- Base image is only ~7MB
- Uses musl libc instead of glibc
- Includes apk package manager
- Security-focused with minimal attack surface
Here's a practical example using Alpine:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]Gotcha!
Some native Node modules may need compilation or may not work with musl libc. You might need to install build dependencies like python3 and make.
Best for: Production containers, microservices, applications without complex dependencies.
Slim Variants
Slim images are a middle ground - they strip out unnecessary packages but keep glibc:
- Smaller than full images, larger than Alpine
- Uses glibc (better compatibility)
- Essential tools included
- Good balance of size and functionality
Best for: Applications that need glibc compatibility but don't require full system tools.
Distroless Images
Google's distroless images contain only your application and runtime dependencies:
- No shell, package manager, or unnecessary tools
- Minimal attack surface
- Very secure for production
- Debugging can be challenging
Best for: Security-critical production workloads, serverless functions.
Scratch Image
The scratch image is completely empty - it's literally an empty filesystem:
- Zero bytes base size
- Only contains what you add
- Perfect for static binaries
- No debugging tools whatsoever
Best for: Compiled languages (Go, Rust), static binaries.
Decision Matrix
| Base Image | Size | Use Case | Pros | Cons |
|---|---|---|---|---|
| Ubuntu/Debian | Large | Development | Full tooling | Huge size |
| Alpine | Very Small | Production | Tiny, secure | Compatibility issues |
| Slim | Medium | Production | Good balance | Still fairly large |
| Distroless | Small | Production | Very secure | Hard to debug |
Practical Recommendations
Start with Alpine
For most applications, Alpine is the sweet spot between size and functionality. It works well for Node.js, Python, and many other runtimes.
Use Slim for Compatibility
If you encounter issues with Alpine (native modules, glibc dependencies), switch to slim variants.
Consider Distroless for Production
For production workloads where security is paramount, distroless images offer minimal attack surface.
Pro Tip
Use multi-stage builds to compile on a full image and run on Alpine or distroless. Best of both worlds!
In the next article, we'll explore Dockerfile labels and how they help with organization and automation. Stay tuned!