Common Docker Mistakes

Docker is powerful but has many pitfalls. This guide covers the most common Docker mistakes including large image layers, dangling images, exposed ports, and provides practical solutions to avoid them.

Large Layers Dangling Images Exposed Ports Common Errors
Mistake 1: Using the 'latest' Tag in Production
Common Mistake

Using `:latest` tag in production

Many developers use `nginx:latest` or `myapp:latest` in production. The `latest` tag is mutable—it changes. This leads to unreproducible deployments and unexpected updates. Different nodes might run different versions of your application.

Solution: Always use specific version tags. Use semantic versioning or git commit SHAs. For production, pin to a specific image digest for absolute immutability.
# Bad - using latest FROM node:latest docker run myapp:latest # Good - using specific version FROM node:18.17.0-alpine3.18 docker run myapp:v1.2.3 # Best - using image digest docker run myapp@sha256:abc123def456...
Mistake 2: Creating Large Image Layers
Common Mistake

Not cleaning up temporary files in the same layer

Each RUN instruction creates a layer. If you install packages and then clean up in a separate RUN, the cleanup doesn't remove files from previous layers—they still exist in the image. This creates large images with unnecessary files.

Solution: Combine commands in a single RUN using `&&`. Clean up temporary files, package caches, and downloaded files in the same layer where they're created.
# Bad - separate layers, large image RUN apt-get update RUN apt-get install -y curl RUN apt-get clean RUN rm -rf /var/lib/apt/lists/* # Good - single layer, small image RUN apt-get update && \ apt-get install -y curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*
Mistake 3: Accumulating Dangling Images
Common Mistake

Not cleaning up unused images and layers

Every time you build a new image, old layers and dangling images (untagged images) accumulate. Over time, this can consume gigabytes of disk space. Many developers don't realize how much space is wasted.

Solution: Regularly run `docker system prune` to clean up unused containers, networks, images, and build cache. Use `docker image prune` for images only.
# Check disk usage docker system df # Remove dangling images only docker image prune # Remove all unused images (including those without tags) docker image prune -a # Complete cleanup (containers, networks, images, cache) docker system prune -a # Remove volumes as well (caution: data loss) docker system prune -a --volumes # Show detailed disk usage docker system df -v
Mistake 4: Exposing Database Ports to the Host
Common Mistake

Publishing database ports (-p 5432:5432) to the host

Many developers publish database ports (5432, 3306, 27017) to the host using `-p`. This exposes your database to anyone who can reach the host. In production, this is a security risk. Databases should only be accessible from within the Docker network.

Solution: Do not publish database ports. Place database containers on a custom internal network. Only application containers should connect to them by service name.
# Bad - exposing database port docker run -d -p 5432:5432 --name postgres postgres # Good - no port exposure, internal only docker network create app-network docker run -d --network app-network --name postgres postgres docker run -d --network app-network --name app -p 8080:3000 myapp # Database is only accessible from 'app' container via 'postgres:5432'
Mistake 5: Running Containers as Root
Common Mistake

Not creating a non-root user in Dockerfile

By default, containers run as root. If an attacker compromises your container, they have root access. This is one of the most common security mistakes in Docker.

Solution: Always create and switch to a non-root user in your Dockerfile. Use the `USER` instruction before the `CMD`.
# Dockerfile with non-root user FROM node:18-alpine # Create non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app COPY --chown=nodejs:nodejs . . # Switch to non-root user USER nodejs CMD ["node", "server.js"]
Mistake 6: Storing Secrets in Environment Variables
Common Mistake

Using environment variables for passwords, API keys

Environment variables are visible in `docker inspect`, `docker exec env`, and logs. They're not secure for secrets. Attackers with container access can read them, and they can leak through debugging output.

Solution: Use Docker secrets (Swarm mode), external secret managers (Vault, AWS Secrets Manager), or mount secret files with restricted permissions.
# Bad - using environment variables for secrets docker run -e DB_PASSWORD=secretpassword postgres # Good - using Docker secrets (Swarm mode) echo "secretpassword" | docker secret create db_password - docker service create --secret db_password --name db postgres # Good - using secret file echo "secretpassword" > db_password.txt chmod 600 db_password.txt docker run -v $(pwd)/db_password.txt:/run/secrets/db_password:ro postgres
Mistake 7: Not Setting Resource Limits
Common Mistake

Running containers without memory or CPU limits

Without limits, a single container can consume all host resources, causing a denial-of-service for other containers. This is especially dangerous in multi-tenant environments.

Solution: Always set memory and CPU limits in production. Start with reasonable limits based on application profiling.
# Bad - no limits docker run -d nginx # Good - with resource limits docker run -d --memory=512m --cpus=0.5 --pids-limit=100 nginx # In docker-compose.yml services: app: image: myapp deploy: resources: limits: cpus: '0.5' memory: 512M
Mistake 8: Not Using .dockerignore
Common Mistake

Sending unnecessary files to Docker daemon

Without `.dockerignore`, the entire build context is sent to the Docker daemon. This includes `node_modules`, `.git`, temporary files, and secrets, making builds slower and images larger.

Solution: Create a `.dockerignore` file to exclude unnecessary files from the build context.
# .dockerignore file node_modules/ npm-debug.log .git/ .gitignore .env *.md Dockerfile .dockerignore .idea/ .vscode/ coverage/ .nyc_output/ *.log
Mistake 9: Hardcoding Configuration in Images
Common Mistake

Hardcoding API endpoints, database URLs in Dockerfile

Hardcoding configuration in images makes them environment-specific. You need separate images for dev, staging, and production, which is wasteful and error-prone.

Solution: Use environment variables or config files mounted at runtime. Keep images environment-agnostic.
# Bad - hardcoded in Dockerfile ENV API_URL=https://api.production.com # Good - use environment variable at runtime ENV API_URL=${API_URL} # Pass at runtime: docker run -e API_URL=https://api.dev.com myapp # Better - use config file mounted at runtime docker run -v $(pwd)/config.json:/app/config.json myapp
Mistake 10: Not Using Multi-Stage Builds
Common Mistake

Including build tools and source code in production images

Production images often contain compilers, SDKs, and source code that aren't needed at runtime. This increases image size, attack surface, and deployment time.

Solution: Use multi-stage builds to separate build environment from runtime environment.
# Multi-stage build # Stage 1: Build (includes compilers) FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o myapp . # Stage 2: Runtime (no compilers) FROM alpine:latest COPY --from=builder /app/myapp . CMD ["./myapp"]
Docker Best Practices Summary
  • Use specific image tags, never `:latest` in production
  • Clean up temporary files in the same RUN layer
  • Regularly prune unused images, containers, and volumes
  • Don't expose database ports to the host
  • Run containers as non-root user
  • Never store secrets in environment variables
  • Always set resource limits (memory, CPU, PIDs)
  • Use `.dockerignore` to exclude unnecessary files
  • Keep images environment-agnostic with environment variables
  • Use multi-stage builds to reduce image size
  • Scan images for vulnerabilities before deployment
  • Use health checks for production containers
Frequently Asked Questions
How do I check what's taking up space in Docker?
Use `docker system df -v` to see detailed breakdown of images, containers, volumes, and build cache usage.
Why is my Docker image so large?
Common causes: not using Alpine base images, not cleaning up package caches, copy large unnecessary files, not using multi-stage builds, or having too many layers.
How often should I clean up Docker resources?
Run `docker system prune -a` weekly in CI/CD environments. For production, set up scheduled jobs or use monitoring to detect disk space issues.
Is it safe to use `docker system prune -a`?
Yes for development. In production, it removes all unused images, which may slow down scaling if images need to be re-pulled. Use `docker image prune -a --filter "until=168h"` to keep images used in the last week.
What's a dangling image?
A dangling image is an image without a tag (repository and tag are `<none>`). They're created when you build a new image with the same name as an existing one without removing the old one.
Should I commit .dockerignore to git?
Yes! `.dockerignore` should be committed to version control so all team members use the same exclusions.
How do I find which container is using a port?
Use `docker ps --filter "publish=8080"` or `lsof -i :8080` on Linux/macOS to see which process is using the port.
What's the difference between a stopped container and a dangling image?
Stopped containers still exist on disk and can be restarted. Dangling images are untagged image layers that are not referenced by any container or tag. Both should be cleaned up regularly.
Previous: Container Performance Next: Docker Interview Questions

Avoid these common Docker mistakes to build more secure, efficient, and maintainable containers. Learn from others' errors and follow best practices from the start.