📌Why Image Quality Matters
A bloated image is slow to build, slow to pull, expensive to store and full of packages attackers can exploit. The same app can ship as a 1 GB image or a 90 MB one — the difference is entirely in the Dockerfile. These are the practices we teach in our Docker course.
📌1. Use Multi-Stage Builds
Build in one stage, run in another, and copy only the artifact.
# Build stageFROM maven:3.9-eclipse-temurin-21 AS buildWORKDIR /appCOPY pom.xml .RUN mvn -q dependency:go-offlineCOPY src ./srcRUN mvn -q package -DskipTests# Runtime stageFROM gcr.io/distroless/java21-debian12COPY --from=build /app/target/app.jar /app.jarUSER nonrootENTRYPOINT ["java", "-jar", "/app.jar"]The Maven toolchain never reaches production, and the final image is a fraction of the size.
📌2. Order Instructions for Caching
Put what changes least at the top. Copy dependency manifests (pom.xml, package.json, requirements.txt) and install dependencies before copying source code, so a code change does not reinstall every dependency.
📌3. Choose a Minimal Base Image
Prefer -slim, Alpine or distroless images over full OS images. Fewer packages means fewer CVEs and faster pulls. Check that your runtime works with Alpine's musl libc before switching.
📌4. Pin Versions
Use a specific tag such as node:22.9-slim (or a digest) instead of latest, so builds are reproducible and upgrades are deliberate.
📌5. Add a .dockerignore
Exclude .git, node_modules, build output, logs and .env files. Builds get faster and you stop leaking secrets into the build context.
📌6. Combine and Clean Up in One Layer
RUN apt-get update && apt-get install -y --no-install-recommends curl \ && rm -rf /var/lib/apt/lists/*Cleaning up in a separate RUN does not shrink the image, because the earlier layer still contains the files.
📌7. Run as a Non-Root User
RUN addgroup --system app && adduser --system --ingroup app appUSER appMany Kubernetes clusters enforce non-root pods through Pod Security Standards, so this also prevents deployment failures.
📌8. Never Bake In Secrets
Do not COPY credentials or set them with ENV or ARG — they stay in the image history. Use BuildKit secret mounts:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci📌9. Use the Exec Form for ENTRYPOINT and CMD
ENTRYPOINT ["node", "server.js"] runs your app as PID 1 so it receives SIGTERM and shuts down gracefully. The shell form wraps it in /bin/sh, which swallows signals.
📌10. Add a Health Check
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/health || exit 1Compose uses it for depends_on conditions. On Kubernetes, use liveness and readiness probes instead.
📌11. Scan Images and Generate an SBOM
Run Trivy or Docker Scout in CI and fail the build on critical vulnerabilities. Generate an SBOM so you can answer "are we affected?" the day a new CVE lands.
📌12. Build in CI, Not on Laptops
Build, tag (with a version and the Git SHA), scan and push from a pipeline. See our GitHub Actions CI/CD tutorial.
📌Quick Checklist
📌Go Further
Ready for orchestration? Read Docker vs Kubernetes and prepare with Docker interview questions. Prakalpana's Docker & Containerization course covers all of this hands-on, live online or 1-on-1. WhatsApp or call +91 9243078181 for a free demo.