Docker in Production: The Mistakes I Keep Making
😅 The Confession
I've been using Docker in production for years. I know the best practices. I've read all the guides. And yet, I keep making the same mistakes.
Here are the Docker mistakes I keep repeating, why they happen, and how I finally learned to avoid them.
Honest truth: Even experienced developers make these mistakes. The key is recognizing them and fixing them.
❌ Mistake #1: Not Using .dockerignore
What I did: Built Docker images that included node_modules, .git, and other unnecessary files.
Result: Images were 2-3x larger than needed. Builds took forever. Wasted storage and bandwidth.
The fix:
# .dockerignore
node_modules
.git
.env
*.log
.DS_Store
coverage
.idea
Impact: Reduced image size from 1.2GB to 450MB. Build time cut in half.
❌ Mistake #2: Running as Root
What I did: Ran containers as root user because it was easier.
Result: Security risk. If container is compromised, attacker has root access.
The fix:
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
Impact: Better security posture. Required for some production environments.
❌ Mistake #3: Not Setting Resource Limits
What I did: Let containers use unlimited CPU and memory.
Result: One container could consume all resources, bringing down the server.
The fix:
# docker-compose.yml
services:
app:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
Impact: Prevents resource exhaustion. Better stability.
❌ Mistake #4: Using Latest Tag
What I did: Used `FROM node:latest` in Dockerfiles.
Result: Unpredictable builds. Different versions on different days. Hard to debug.
The fix:
# Use specific version
FROM node:20.11.0-alpine
Impact: Reproducible builds. Easier debugging.
❌ Mistake #5: Not Using Multi-Stage Builds
What I did: Included build tools in production images.
Result: Larger images. More attack surface. Slower deployments.
The fix:
# Multi-stage build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --production
CMD ["node", "dist/index.js"]
Impact: Reduced image size by 60%. Faster deployments.
❌ Mistake #6: Not Health Checking
What I did: No health checks in containers.
Result: Containers could be running but broken. No automatic recovery.
The fix:
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:3000/health || exit 1
Impact: Automatic detection of broken containers. Better reliability.
❌ Mistake #7: Storing Secrets in Images
What I did: Hardcoded API keys and passwords in Dockerfiles.
Result: Secrets exposed in image layers. Security risk.
The fix:
# Use environment variables
services:
app:
environment:
- API_KEY=${API_KEY}
secrets:
- db_password
Impact: Secrets not in images. Better security.
📊 Impact Summary
| Mistake | Impact | Fix Impact |
|---|---|---|
| No .dockerignore | 2-3x larger images | 60% size reduction |
| Running as root | Security risk | Better security |
| No resource limits | Resource exhaustion | Stable performance |
| Using latest tag | Unpredictable builds | Reproducible builds |
💡 How I Avoid These Now
- Checklist before deployment: I have a checklist I review before every production deploy
- Linting: Use hadolint to check Dockerfiles
- Automated scanning: Scan images for vulnerabilities
- Code review: Always review Docker changes
- Documentation: Document why we do things a certain way
🎯 Key Takeaways
- Even experienced developers make Docker mistakes
- Most mistakes are about security, size, or reliability
- Simple fixes have big impacts
- Use checklists and tools to catch mistakes
- Learn from each mistake—don't repeat them
I still make mistakes, but now I catch them faster and fix them before they hit production. That's progress.