<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5" -->

---
title: 5 Dockerfile “Best Practices” That Are Actually Wrong
description: Five common Dockerfile misconceptions are debunked with practical demos. First, Alpine images use musl libc instead of glibc, causing compatibility failures...
canonical: https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: 5 Dockerfile “Best Practices” That Are Actually Wrong | daily.dev
og:description: Five common Dockerfile misconceptions are debunked with practical demos. First, Alpine images use musl libc instead of glibc, causing compatibility failures...
og:url: https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5
og:image: https://api.daily.dev/og/posts/NnA4RnDI5.png
og:image:alt: 5 Dockerfile “Best Practices” That Are Actually Wrong
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 5 Dockerfile “Best Practices” That Are Actually Wrong

**[DevOps Toolbox](https://daily.dev/sources/devopstoolbox)** · 15 min read · 54 upvotes · 0 comments

## Summary

Five common Dockerfile misconceptions are debunked with practical demos. First, Alpine images use musl libc instead of glibc, causing compatibility failures with many native packages — Slim is usually the better tradeoff. Second, Docker layer caching is frequently abused by copying source code before installing dependencies, forcing full reinstalls on every code change. Third, sending unnecessary files into the build context (node_modules, logs, artifacts) bloats builds — .dockerignore solves this cleanly. Fourth, multi-stage builds for compiled languages like Go can shrink images from 272MB down to 2.3MB using scratch or distroless base images. Fifth, the 'one process per container' rule is a guideline, not a law — supervisord is a practical solution when running nginx alongside an app server without Kubernetes overhead. A bonus tip covers pinning images by digest hash rather than mutable tags to prevent unexpected base image changes. The Rust tool 'd-roast' is mentioned for linting Dockerfiles in local and CI environments.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.youtube.com/watch?v=aZ_y2M2OuEA>

## Questions this post answers

### Why does my Python app fail to install packages like confluent-kafka on the Alpine Docker image but works on slim?

Alpine Linux uses musl libc instead of glibc, which is what most compiled Python package releases (like confluent-kafka) are built against. Because musl does not equal glibc, pip cannot find a matching prebuilt wheel and either fails or falls back to compiling from source, which can also fail without build tools installed. Switching to the slim base image, which uses glibc, resolves the mismatch.

_Developers weighing alpine against slim for their images can compare real tradeoffs like this on daily.dev._

### How do I structure a Dockerfile so that code changes don't invalidate my npm install cache layer?

Copy only package.json and the lock file first, run npm ci to install dependencies, and only afterward copy the rest of the application source code. Docker caches layers based on whether their inputs changed, so if source code is copied before dependencies are installed, every code edit invalidates and reruns the dependency install step, wasting build time.

_Anyone optimizing CI build times can dig into caching strategies like this on daily.dev._

### How much smaller can a Go Docker image get by using a multi-stage build with a scratch base instead of alpine?

A Go binary built on an alpine base image weighed about 272 megabytes, dropped to roughly 11 megabytes using a multi-stage build that copies only the compiled binary into a fresh builder stage, and shrank further to about 2.3 megabytes when the final stage used a from-scratch base with no operating system at all. Distroless images offer a middle ground with useful runtime basics and better defaults than scratch.

_Engineers chasing smaller container images can track multi-stage build patterns like this on daily.dev._

## Similar posts on daily.dev

- [Building smaller Docker images faster](https://daily.dev/posts/building-smaller-docker-images-faster-pkye16mw7) · Lobsters · 3 upvotes · 0 comments
- [Why To Use A Multi-Stage Dockerfile](https://daily.dev/posts/why-to-use-a-multi-stage-dockerfile-ripe33vzt) · RUBYLAND · 0 upvotes · 0 comments
- [Dockerfile Practices are a DevOps Tax Before They are a Security Concern](https://daily.dev/posts/dockerfile-practices-are-a-devops-tax-before-they-are-a-security-concern-fhyvgtzhu) · Cloud Native Now · 34 upvotes · 0 comments
- [Small and secure Docker images for Rust: scratch vs alpine vs debian](https://daily.dev/posts/small-and-secure-docker-images-for-rust-scratch-vs-alpine-vs-debian-31lvdlshh) · Sylvain Kerkour · 0 upvotes · 0 comments

---

Tags: [#golang](https://daily.dev/tags/golang), [#docker](https://daily.dev/tags/docker), [#containers](https://daily.dev/tags/containers)

[View this post on daily.dev](https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"5 Dockerfile “Best Practices” That Are Actually Wrong","url":"https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5"},"datePublished":"2026-07-17T13:49:02.960Z","dateModified":"2026-09-14T07:38:17.811Z","description":"Five common Dockerfile misconceptions are debunked with practical demos. First, Alpine images use musl libc instead of glibc, causing compatibility failures...","image":"https://i.ytimg.com/vi/aZ_y2M2OuEA/sddefault.jpg","thumbnailUrl":"https://i.ytimg.com/vi/aZ_y2M2OuEA/sddefault.jpg","isAccessibleForFree":true,"articleSection":"DevOps Toolbox","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"DevOps Toolbox","logo":"https://media.daily.dev/image/upload/s--zlfrGvN5--/f_auto/v1752671176/logos/devopstoolbox","url":"https://daily.dev/sources/devopstoolbox"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":54},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"golang,docker,containers","timeRequired":"PT15M","video":{"@type":"VideoObject","name":"5 Dockerfile “Best Practices” That Are Actually Wrong","description":"Five common Dockerfile misconceptions are debunked with practical demos. First, Alpine images use musl libc instead of glibc, causing compatibility failures...","thumbnailUrl":"https://i.ytimg.com/vi/aZ_y2M2OuEA/sddefault.jpg","uploadDate":"2026-07-17T13:49:02.960Z","duration":"PT15M","url":"https://api.daily.dev/r/NnA4RnDI5","embedUrl":"https://www.youtube.com/embed/aZ_y2M2OuEA"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"DevOps Toolbox","item":"https://daily.dev/sources/devopstoolbox"},{"@type":"ListItem","position":3,"name":"5 Dockerfile “Best Practices” That Are Actually Wrong"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/5-dockerfile-best-practices-that-are-actually-wrong-nna4rndi5#faq","mainEntity":[{"@type":"Question","name":"Why does my Python app fail to install packages like confluent-kafka on the Alpine Docker image but works on slim?","acceptedAnswer":{"@type":"Answer","text":"Alpine Linux uses musl libc instead of glibc, which is what most compiled Python package releases (like confluent-kafka) are built against. Because musl does not equal glibc, pip cannot find a matching prebuilt wheel and either fails or falls back to compiling from source, which can also fail without build tools installed. Switching to the slim base image, which uses glibc, resolves the mismatch. Developers weighing alpine against slim for their images can compare real tradeoffs like this on daily.dev."}},{"@type":"Question","name":"How do I structure a Dockerfile so that code changes don't invalidate my npm install cache layer?","acceptedAnswer":{"@type":"Answer","text":"Copy only package.json and the lock file first, run npm ci to install dependencies, and only afterward copy the rest of the application source code. Docker caches layers based on whether their inputs changed, so if source code is copied before dependencies are installed, every code edit invalidates and reruns the dependency install step, wasting build time. Anyone optimizing CI build times can dig into caching strategies like this on daily.dev."}},{"@type":"Question","name":"How much smaller can a Go Docker image get by using a multi-stage build with a scratch base instead of alpine?","acceptedAnswer":{"@type":"Answer","text":"A Go binary built on an alpine base image weighed about 272 megabytes, dropped to roughly 11 megabytes using a multi-stage build that copies only the compiled binary into a fresh builder stage, and shrank further to about 2.3 megabytes when the final stage used a from-scratch base with no operating system at all. Distroless images offer a middle ground with useful runtime basics and better defaults than scratch. Engineers chasing smaller container images can track multi-stage build patterns like this on daily.dev."}}]}
```

