DEV Community

Cover image for Common Docker Mistakes That Slow Your CI/CD (And How to Fix Them)
Abdulmuhaimin1219
Abdulmuhaimin1219

Posted on

Common Docker Mistakes That Slow Your CI/CD (And How to Fix Them)

10 Docker Anti-Patterns (And How to Fix Them)

I have audited many Dockerfiles across DevOps teams and startups. Despite differences in team size, maturity, and industry, the same architectural mistakes appear repeatedly.

These issues are rarely the result of negligence or lack of skill. Docker is easy to adopt but difficult to use correctly at scale. A Dockerfile copied from a tutorial may build successfully and pass initial tests, which makes it tempting to move on without revisiting it. Months later, teams are left diagnosing 15+ minute CI builds, multi-gigabyte images, and vulnerability scanners reporting critical findings.

This article outlines ten Docker anti-patterns that commonly appear in production environments and provides concrete, low-effort changes to correct them.

1. Non-Deterministic Base Images

The Anti-Pattern: Using the latest tag.

FROM node:latest 
Enter fullscreen mode Exit fullscreen mode
  1. Using latest as a base image

Using latest creates a moving target. A build that works on Friday may fail on Saturday if the upstream image changes, breaking native dependencies or introducing incompatible updates.

Fix: Pin exact versions (runtime + OS variant) for reproducible builds. This ensures identical images every time. For maximum immutability, use digest pinning @sha256:… in CI/CD. In 2026, prefer Debian-based slim images for broad compatibility.

FROM node:20.11.1-bookworm-slim@sha256:<digest>
Enter fullscreen mode Exit fullscreen mode

2. Bloated Base Images

Anti-pattern: Treating containers like mini VMs.

FROM ubuntu:24.04
RUN apt-get update && apt-get install -y nodejs npm
Enter fullscreen mode Exit fullscreen mode

This works, which is why it survives code review.

But full OS images quietly tax everything downstream: larger pulls, slower CI, noisier vulnerability scans, and patch cycles tied to an entire distribution instead of your runtime. None of this hurts locally. It only shows up once you operate at scale.

The Fix: Start from the minimum executable surface, not a familiar OS.

Your base image should exist for one reason only: to run the compiled artifact or runtime process. Anything beyond that is technical debt with interest.

For Node.js in 2026, the practical hierarchy looks like this:

FROM node:20.17.0-bookworm-slim
Enter fullscreen mode Exit fullscreen mode
  • Debian bookworm-slim remains the default choice for production
  • glibc avoids native module surprises
  • small enough to be efficient, large enough to be boring (boring is good)
FROM node:20.17.0-alpine
Enter fullscreen mode Exit fullscreen mode
  • Viable when image size is critical
  • musl libc will surface edge cases in native dependencies
  • requires explicit testing discipline, not hope
FROM cgr.dev/chainguard/node:20
Enter fullscreen mode Exit fullscreen mode
  • No shell, no package manager, no OS noise
  • Forces runtime immutability by design
  • Ideal for hardened production tiers and regulated environments

Switching off full OS images typically cuts image size 80–95%, reduces CVE noise dramatically, and shortens CI pipelines without changing application code.

3. Inefficient Build Caching

Anti-pattern: Busting the dependency cache on every commit.

FROM node:20.17.0-bookworm-slim
WORKDIR /app
COPY . .
RUN npm install
Enter fullscreen mode Exit fullscreen mode

This invalidates the most expensive layer in your build every time any source file changes. CI still “works”, but build times quietly degrade as the repo grows.

At scale, this shows up as slow pipelines, wasted compute, and developers normalising 10–15 minute builds.

Fix: Order layers by change frequency. Let Docker cache do its job.

FROM node:20.17.0-bookworm-slim
WORKDIR /app

COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

COPY . .
Enter fullscreen mode Exit fullscreen mode
  • Dependency manifests change rarely → cached long-term
  • Application code changes frequently → cheap rebuilds
  • BuildKit cache mounts persist npm artifacts across builds

This pattern turns dependency installs from a recurring cost into a near one-time operation.

4. Violation of Least Privilege (Root User)

Anti-pattern: Accepting the default user.

By default, containers run as root. If a process is compromised and escapes the container, it escapes as root. That’s not theoretical; that’s how real incidents cascade.

Fix: Drop privileges explicitly.

Most modern runtime images already ship with an unprivileged user. Use it.

FROM node:20.17.0-bookworm-slim
WORKDIR /app

RUN chown -R node:node /app
USER node

COPY --chown=node:node . .
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

This is baseline defense-in-depth for production, not an “advanced hardening” step.

5. Hardcoded Secrets

Anti-pattern: Baking credentials into images.

ENV AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
Enter fullscreen mode Exit fullscreen mode

Secrets embedded in images persist forever in layers, registries, caches, and backups. Rotating them doesn’t remove them.

Fix: Inject secrets at runtime or mount them ephemerally at build time.

# Build-time secret (e.g., private npm registry)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci --omit=dev
Enter fullscreen mode Exit fullscreen mode

Runtime injection (Docker/Kubernetes secrets, env vars) is safest.
If a secret ever appears in docker history, it’s already leaked.

6. Leaking the Build Context

Anti-pattern: Shipping your entire repo to the Docker daemon.

No .dockerignore means .git, node modules, test artifacts, and editor configs are sent on every build. This bloats context uploads, invalidates caches, and slows CI for no functional reason.

Fix: Be aggressively explicit about what doesn’t belong in the image.

.git
node_modules
.env
coverage
.vscode
dist
build
Dockerfile
Enter fullscreen mode Exit fullscreen mode

On large repositories, this alone can shave minutes off CI builds.

7. Lack of Observability (Health Checks)

Anti-pattern: Assuming “running = healthy.”

A process can be alive but deadlocked, DB-disconnected, or otherwise non-functional. Without health indicators, orchestrators keep sending traffic to zombies. CI/CD and production monitoring silently degrade.

Fix: Define explicit health checks.

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
Enter fullscreen mode Exit fullscreen mode

In Kubernetes, mirror this with liveness and readiness probes.
Orchestrators then restart stuck containers or reroute traffic automatically, with no human intervention needed.

8. Monolithic Dockerfiles

Anti-pattern: One Dockerfile for everything.

Dev tools like git and curlin production? Yep. Bloated images, unnecessary attack surface, slower deployments. This “convenience-first” approach rarely pays off beyond local testing.

Fix: Multi-stage builds. Keep only what’s needed at runtime.

# syntax=docker/dockerfile:1

FROM node:20.17.0-bookworm-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

FROM node:20.17.0-bookworm-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]

Enter fullscreen mode Exit fullscreen mode

The final image contains only runtime artifacts.
Smaller. Safer. Predictable. No surprises in prod.

9. Inefficient Layering

Anti-pattern: Multiple RUNs without cleanup.

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
Enter fullscreen mode Exit fullscreen mode

Deleting files in one layer doesn’t shrink previous layers image size still balloons. At scale, this is silent infrastructure debt.

Fix: Chain commands and clean in the same layer.

RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

Temporary files never persist. Cache efficiency and image size stay predictable. This is how production-grade engineers think about every layer: no surprises, minimal footprint.

10. Stale Base Images

Anti-pattern: Set and forget.

A base image untouched for months is a CVE time bomb. It doesn’t matter that the app works attacks don’t care either.

Fix: Scan + automate updates.

  • Scan images in CI: docker scout cves or Trivy
  • Automate base bumps: Renovate, Dependabot, GitHub-native PRs
  • Cadence: monthly (weekly if security-critical)
  • Always test updates in staging before prod

Example Renovate snippet:

{
  "packageRules": [
    {
      "matchManagers": ["dockerfile"],
      "schedule": ["every weekend"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This combination keeps CVEs low with minimal manual effort.

Impact

Applied together, these 10 fixes typically:

  • Cut image size 70–90%
  • Reduce CI build times 50–80%
  • Lower vulnerability counts dramatically

Start with the worst offender run docker scout cvesor trivy image today. Most changes are one-line or small-block diffs, but the operational payoff is massive.

Most Docker problems aren’t Docker problems at all. They’re the result of copying a Dockerfile that worked once and never revisiting it. Treating container configuration as static, instead of something that evolves with your application and platform. I’ve been guilty of this too everyone has. We’re busy, and if it works, we move on.

But small inefficiencies compound fast. That 4-minute build multiplies into hours per week. That bloated image racks up storage and transfer costs. That root user? A real security risk, not just a talking point in a blog.

Here’s a pragmatic approach: pick one fix from this list either the one that irritates you most or the one that’s easiest to implement, and do it this week. Then tackle another next week.

You won’t transform your Docker setup overnight. But in a month, you’ll have smaller images, faster builds, and fewer 2 a.m. pages. That incremental improvement is worth every minute.

Top comments (0)