7 Common Docker Mistakes Beginners Make and How to Fix Them
Docker broke my production build last month. I spent four hours debugging why my app worked locally but crashed in production. The culprit? I used node:latest in my Dockerfile, and Node.js had released a new version overnight. My dependencies weren’t compatible.
That mistake taught me something important. Docker’s simplicity hides traps that bite beginners repeatedly. I’ve made most of these mistakes myself. Here are the seven most common ones, why they cause problems, and exactly how to fix each one.
Mistake 1: Using :latest Tags
I used to think :latest meant “the most recent stable version.” It doesn’t. It just means “whatever was built last.”
When you pull node:latest, you get whatever version the Docker Hub maintainers pushed most recently. If that version changes between builds, your application might break. You won’t know why because you didn’t change anything.
The fix: Pin specific versions.
# WRONG: UnpredictableFROM node:latest
# CORRECT: ReproducibleFROM node:18.17-alpinePin both the major version and the specific release. This ensures every build pulls the exact same image. When you want to upgrade, you do it intentionally, not by accident.
Mistake 2: Missing .dockerignore
My first Docker image was 2.3 GB. I couldn’t understand why. Then I discovered I was copying my entire .git folder, node_modules, build artifacts, and even .env files into the image.
Docker copies everything in your build context unless you tell it not to. Without a .dockerignore file, you bloat your images and potentially expose sensitive data.
The fix: Create a .dockerignore file.
.git.gitignorenode_modulesnpm-debug.loglogs.env.env.local*.md.DS_Storecoverage.nyc_outputAdd this file to your project root. Docker will exclude these files from the build context. Your images become smaller, builds become faster, and you avoid accidentally including secrets.
Mistake 3: Running Containers as Root
Docker containers run as root by default. I didn’t think much about this until I read about container escape vulnerabilities. If an attacker compromises your container, they have root access inside it. That’s one step closer to your host system.
The fix: Create a non-root user in your Dockerfile.
FROM node:18-alpine
# Create non-root userRUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /appCOPY --chown=appuser:appgroup . .
# Switch to non-root userUSER appuser
CMD ["node", "index.js"]This adds a few lines but significantly improves security. The container can only access what the appuser can access, limiting damage from potential breaches.
Mistake 4: Not Cleaning Up Orphaned Resources
Docker doesn’t automatically delete old images, stopped containers, or unused volumes. They pile up like unwashed dishes. After a few months of development, I had 47 GB of Docker junk on my machine.
The fix: Run cleanup commands regularly.
# Remove stopped containers, unused networks, and dangling imagesdocker system prune
# More aggressive: remove all unused images and volumes# WARNING: This deletes images not currently in usedocker system prune -a --volumes
# Remove only orphaned images (no container references)docker image prune
# Remove unused volumesdocker volume pruneRun docker system df first to see how much space Docker is using. Then decide which cleanup level you need. I run docker system prune weekly.
Mistake 5: Confusing Images and Containers
I edited my code and ran docker run my-app. The app still showed the old behavior. I was confused for an hour.
The problem was thinking “running” an image updates it. It doesn’t. Images are templates. Containers are instances. You need to rebuild the image first.
The fix: Always rebuild after changes.
# 1. Make code changes# 2. Rebuild the imagedocker build -t my-app .
# 3. Stop old containerdocker stop my-containerdocker rm my-container
# 4. Run new containerdocker run -d --name my-container my-appOr use Docker Compose, which handles this better:
docker-compose up --buildThis rebuilds images and recreates containers in one command.
Mistake 6: Hardcoding Secrets in Dockerfiles
I’ve seen Dockerfiles with database passwords directly in the file. That’s a security disaster waiting to happen. Even if you don’t push to a public registry, secrets in Dockerfiles leak through version control, logs, and image layers.
The fix: Use environment variables or Docker secrets.
services: app: build: . environment: - DB_PASSWORD=${DB_PASSWORD} - API_KEY=${API_KEY}Create a .env file locally:
DB_PASSWORD=your_secure_passwordAPI_KEY=your_api_keyAdd .env to your .gitignore immediately. Never commit it.
For production, use Docker Swarm secrets or Kubernetes secrets. These mount secrets as files or inject them securely at runtime.
Mistake 7: Stuffing Multiple Services in One Container
I once put Node.js, Redis, and a cron job scheduler in one container. It worked, but it violated Docker’s core principle: each container should do one thing well.
That monolithic container was hard to debug, hard to scale, and hard to update. When Redis crashed, the whole container went down.
The fix: Use Docker Compose for multiple services.
services: backend: build: ./backend ports: - "5000:5000" depends_on: - db - redis
frontend: build: ./frontend ports: - "3000:3000" depends_on: - backend
db: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: ${DB_PASSWORD}
redis: image: redis:7-alpine
volumes: postgres_data:Each service runs in its own container. You can scale them independently, restart them separately, and debug them in isolation.
Quick Reference Table
Here’s a summary of each mistake and its fix:
| Mistake | Consequence | Fix |
|---|---|---|
Using :latest tag | Unpredictable builds, breaks when images update | Pin versions like node:18.17 |
Missing .dockerignore | Bloated images, secrets leak, slow builds | Exclude .git, node_modules, .env |
| Running as root | Security vulnerability | Add non-root user with USER appuser |
| Not cleaning up | Disk space wasted (GBs of junk) | Run docker system prune regularly |
| Image vs container confusion | Changes don’t appear | Rebuild image before running |
| Hardcoding secrets | Credentials leak | Use .env files and Docker secrets |
| Monolithic containers | Hard to debug, scale, and update | Use Compose, one service per container |
Why These Fixes Matter
Each mistake compounds over time. A missing .dockerignore slows every build. An unpinned :latest tag breaks deployments randomly. Root containers create security vulnerabilities that attackers can exploit.
But the fixes are straightforward. Add a .dockerignore file today—it takes two minutes. Pin your image versions. Create non-root users. These small changes make Docker reliable instead of frustrating.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments