2024-05-15Hünkar Döner

How Should an Ideal Dockerfile Be? Best Practices

DockerBest PracticesSecurityContainer
H

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 musl instead of glibc, 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:
    RUN apt-get update && apt-get install -y python3 \
        && rm -rf /var/lib/apt/lists/*
    
    (Note: Cleaning cache with apt-get clean reduces 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.