How Should an Ideal Dockerfile Be? Best Practices
How Should an Ideal Dockerfile Be?
Dockerizing an application is not just writing FROM ubuntu and installing everything inside. A poorly written Dockerfile leads to security vulnerabilities, huge image sizes, and slow build times.
Here are the best practices for an ideal Dockerfile:
1. Choose the Right Base Image
Instead of large, general-purpose images like ubuntu or debian, prefer minimal images specific to your application.
- Alpine: Very small (5MB) but uses
muslinstead ofglibc, which might cause compatibility issues. - Distroless: Developed by Google, these are the safest images containing only what is necessary to run the application, without even a shell (bash).
2. Use Multi-Stage Builds
For compiled languages like Go or Java, do not carry build tools (compiler, maven) to the final image. Copy only the compiled binary to the final image using multi-stage build.
# Stage 1: Build
FROM golang:1.16 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Stage 2: Runtime
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
3. Reduce Layer Count
Every RUN, COPY, and ADD command creates a new layer. Combine commands into a single RUN command by chaining.
- Bad:
RUN apt-get update RUN apt-get install -y python3 - Good:
(Note: Cleaning cache withRUN apt-get update && apt-get install -y python3 \ && rm -rf /var/lib/apt/lists/*apt-get cleanreduces image size.)
4. Disable Root User
Containers run as root by default. This is a huge security risk. Create a specific user to run your application.
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
5. Use .dockerignore
Create a .dockerignore file to prevent copying unnecessary files like git history, test files, or local node_modules folder into the image. This speeds up build time (context sending).
6. Version Pinning
Do not use tags like node:latest. This can cause a build that works today to break tomorrow. Always use specific versions: node:14.17.0-alpine.