Cloud Native 29 min read

How to Shrink Docker Images by 70% and Harden Them with Trivy

This guide explains how to dramatically shrink Docker image sizes by up to 70% using multi‑stage builds, Alpine or Distroless bases, layer merging, .dockerignore, and BuildKit, while also integrating Trivy security scanning, non‑root users, SUID removal, and CI/CD automation to ensure a lean, secure container deployment.

Raymond Ops
Raymond Ops
Raymond Ops
How to Shrink Docker Images by 70% and Harden Them with Trivy

Applicable Scenarios & Prerequisites

Target container images larger than 500 MB, build time >10 min, with high‑severity CVEs. Supported OS: RHEL/CentOS 7.9+ or Ubuntu 20.04+. Requires Docker 20.10+ (or Podman 3.0+), Trivy 0.40+.

Anti‑Pattern Warnings

Debug environments need full toolchains; avoid over‑optimising.

Legacy apps that depend on specific glibc versions may break on Alpine (musl).

Extreme performance requirements can suffer 5‑10 % slowdown on Alpine.

Compliance that requires audit logs may be broken by removing log files.

Teams unfamiliar with Alpine should consider training or use Debian‑Slim.

Environment & Version Matrix

OS version : RHEL 8.7+ / CentOS Stream 9 or Ubuntu 22.04 LTS

Kernel version : 4.18.0‑425+ (RHEL) or 5.15.0‑60+ (Ubuntu)

Docker : 24.0.7 (official repo)

Podman : 4.6.1 (RHEL) or 4.3.1 (apt)

Trivy : 0.48.3

Quick Checklist

Check current image size: docker images | grep your-image Backup Dockerfile: cp Dockerfile Dockerfile.bak Install Trivy (e.g., brew install aquasecurity/trivy/trivy or script)

Enable Docker BuildKit:

export DOCKER_BUILDKIT=1

Implementation Steps

Architecture Overview

A Docker image consists of a base layer, dependency layers, application layer, and configuration layer. Each Dockerfile instruction ( RUN, COPY, ADD) creates a new read‑only layer; a writable top layer is added at runtime.

Image = Base Layer + Dependency Layers + Application Layer + Config Layer

Optimization Strategies

Replace the base image (e.g., ubuntu:22.04alpine:3.18distrolessscratch).

Use multi‑stage builds to separate build‑time dependencies from runtime.

Merge multiple RUN commands and clean caches in the same layer.

Use a .dockerignore file to exclude source control, documentation, tests, and build artifacts.

Enable BuildKit for parallel builds and better caching.

Security hardening: run as a non‑root user, remove SUID binaries, set a read‑only root filesystem, and enable Docker Content Trust.

Sample Optimized Dockerfile (Python)

# ---------- Stage 1: Builder ----------
FROM python:3.11-alpine3.18 AS builder
WORKDIR /app
# Install build dependencies and create a virtual environment
RUN apk add --no-cache gcc musl-dev postgresql-dev libffi-dev && \
    python -m venv /opt/venv && \
    /opt/venv/bin/pip install --no-cache-dir -r requirements.txt

# ---------- Stage 2: Runtime ----------
FROM python:3.11-alpine3.18
WORKDIR /app
# Install only runtime dependencies
RUN apk add --no-cache libpq libffi && rm -rf /var/cache/apk/*
COPY --from=builder /opt/venv /opt/venv
COPY --chown=appuser:appuser . .
# Create a non‑root user
RUN addgroup -S appuser && adduser -S appuser -G appuser
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --quiet --tries=1 --spider http://localhost:8000/health || exit 1
CMD ["python","app.py"]

Build and Compare

# Original image (Ubuntu)
docker build -t myapp:ubuntu .
# Optimized image (Alpine)
docker build -t myapp:alpine .
# Compare sizes
docker images | grep myapp
# Example output:
# myapp:ubuntu 1.2GB → myapp:alpine 350MB → myapp:multistage 120MB

Security Scanning with Trivy

# Scan the optimized image
trivy image --severity HIGH,CRITICAL myapp:alpine
# After fixing vulnerabilities
trivy image --severity HIGH,CRITICAL myapp:secure

Fix by upgrading the base image (e.g., python:3.11-alpine3.19) or updating vulnerable packages in requirements.txt.

CI/CD Integration

GitLab CI example:

stages:
  - build
  - scan
  - deploy

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

security_scan:
  stage: scan
  image: aquasec/trivy:latest
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  allow_failure: false

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main

GitHub Actions example (using aquasecurity/trivy-action) can upload SARIF results to GitHub Security.

Monitoring Image Size

Export image sizes for Prometheus node‑exporter:

# /opt/monitoring/docker_image_size.sh
TEXTFILE="/var/lib/node_exporter/textfile_collector/docker_images.prom"

docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | while read line; do
  IMAGE=$(echo $line | awk '{print $1}')
  SIZE=$(echo $line | awk '{print $2}' | numfmt --from=iec)
  echo "docker_image_size_bytes{image=\"$IMAGE\"} $SIZE"
done > $TEXTFILE

Common Issues & Troubleshooting

Alpine container fails at runtime – usually due to musl vs glibc incompatibility. Switch to Debian‑Slim or rebuild dependencies for musl.

Image size not reduced – check for unmerged RUN layers; combine commands and clean caches in a single layer.

Build cache invalidated – ensure COPY of source files occurs after installing dependencies.

Permission errors in container – verify file ownership; use COPY --chown=appuser:appuser and run as non‑root.

Trivy scan timeout – update the vulnerability database ( trivy image --download-db-only) or use a local mirror.

FAQ

Why does Alpine cause compatibility issues? Alpine uses musl libc; some compiled binaries expect glibc. Use Debian‑Slim or rebuild dependencies for musl.

Can I debug a multi‑stage image? The runtime image has no tools; keep a debug stage or use docker exec -u root / kubectl debug.

How to identify removable files? Use dive myapp:latest to inspect layer contents.

Distroless images have no shell – how to troubleshoot? Attach a temporary debug container with busybox or use kubectl debug.

Can Trivy miss vulnerabilities? It relies on CVE databases that may lag; consider additional scanners like Clair or Anchore.

Is a minimal image mandatory for production? Not required; Debian‑Slim (~100 MB) offers a good balance between size and debuggability.

How to handle npm/yarn caches? Run

npm ci --only=production && npm cache clean --force && rm -rf /root/.npm /tmp/*

.

How to build multi‑arch images? Use Docker Buildx:

docker buildx create --use && docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .

.

Is image signing required? Not mandatory but strongly recommended for supply‑chain security.

How to automate image optimization? Tools like docker-slim can analyze and produce a smaller image automatically.

One‑Click Optimization Script

#!/bin/bash
set -e

IMAGE_NAME=${1:-myapp}
IMAGE_TAG=${2:-latest}

# Backup original Dockerfile
cp Dockerfile Dockerfile.bak.$(date +%Y%m%d_%H%M%S)

# Enable BuildKit
export DOCKER_BUILDKIT=1

# Build optimized image
time docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .

# Show image size and layer history
docker images ${IMAGE_NAME}:${IMAGE_TAG}
docker history ${IMAGE_NAME}:${IMAGE_TAG} --no-trunc

# Security scan (if Trivy is installed)
if command -v trivy >/dev/null 2>&1; then
  trivy image --severity HIGH,CRITICAL ${IMAGE_NAME}:${IMAGE_TAG}
else
  echo "Trivy not installed – skipping scan"
fi

echo "Optimized image size: $(docker images ${IMAGE_NAME}:${IMAGE_TAG} --format "{{.Size}}")"

Run with

chmod +x optimize_dockerfile.sh && ./optimize_dockerfile.sh myapp v2.0

.

References

Docker best practices: https://docs.docker.com/develop/dev-best-practices/

Dockerfile reference: https://docs.docker.com/engine/reference/builder/

Trivy documentation: https://aquasecurity.github.io/trivy/

Hadolint linter: https://github.com/hadolint/hadolint

Distroless images: https://github.com/GoogleContainerTools/distroless

Docker Slim: https://github.com/docker-slim/docker-slim

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerImage Optimizationci/cdAlpinemulti-stage-buildsecurity scanningTrivy
Raymond Ops
Written by

Raymond Ops

Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.