How to Optimize Dockerfile
✅ 1. Use a Minimal Base Image
Before:
FROM ubuntu:20.04
After (optimized):
FROM alpine:3.20
Alpine is only ~5MB. Use it unless you need full OS features.
✅ 2. Use Multi-Stage Builds
Before:
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
After (optimized):
# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Runtime stage
FROM alpine:3.20
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
Reduces final image size significantly by excluding build tools.
✅ 3. Minimize Layers
Before:
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
After (optimized):
RUN apt-get update && \
apt-get install -y curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Fewer layers = smaller image + faster builds.
✅ 4. Use .dockerignore
Add a .dockerignore
file:
nginx
node_modules
.git
*.log
Dockerfile
README.md
Prevents unnecessary files from being sent to Docker build context.
✅ 5. Leverage Build Cache
Order Dockerfile instructions from least to most frequently changed.
Good Order:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
Reuse cache for dependencies if code changes frequently.
✅ 6. Avoid Running as Root
Add a user:
RUN addgroup app && adduser -S -G app appuser
USER appuser
Improves container security.
✅ 7. Clean Up Temporary Files
Remove caches, build artifacts, and logs:
RUN make build && rm -rf /tmp/*
✅ 8. Pin Versions
Pin base images and packages for reproducibility.
FROM node:18.20.2-alpine
RUN apk add --no-cache curl=8.4.0-r0
✅ 9. Combine COPY and RUN Efficiently
Before:
COPY script1.sh .
COPY script2.sh .
RUN chmod +x script1.sh script2.sh
After:
COPY script*.sh ./
RUN chmod +x script*.sh
✅ 10. Use Distroless or Scratch (Advanced)
Distroless:
FROM gcr.io/distroless/static-debian11
COPY myapp /
CMD ["/myapp"]
No package manager, shell, or utilities — ultra-minimal and secure.
๐ Final Tip: Test Your Image Size
After building:
docker images
docker image inspect <image>
Sample Optimized Dockerfile (Go Example)
# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . .
RUN go build -o server
# Runtime stage
FROM alpine:3.20
RUN adduser -S -G app appuser
WORKDIR /home/appuser
COPY --from=builder /app/server .
USER appuser
EXPOSE 8080
ENTRYPOINT ["./server"]
What is a Multi-Stage Build in Docker?
Multi-stage builds allow you to use multiple FROM
statements in a single Dockerfile to separate the build environment from the runtime environment.
✅ Why Use Multi-Stage Builds?
Benefit | Explanation |
---|---|
๐งน Smaller Images | Final image contains only what's needed to run the app. No compilers or dev tools. |
๐ More Secure | No leftover build artifacts or secrets. |
⚡ Faster Deployment | Less data to push/pull. |
๐ฆ Cleaner Architecture | Build logic is separated from runtime logic. |
๐ ️ Example: Without vs With Multi-Stage Build
❌ Without Multi-Stage (All-in-one)
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
Problem: Final image contains Go compiler, build cache, and source code.
Image Size: ~1 GB+
✅ With Multi-Stage Build
# Stage 1: Builder
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Stage 2: Final runtime image
FROM alpine:3.20
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
Final Image: Only has /myapp
binary.
No source code, no compiler, just what's needed.
Image Size: ~20 MB
๐งฉ Real Use Case: Node.js with Multi-Stage
# Build Stage
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production Stage
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
๐ 1. Python (FastAPI + Uvicorn) – Multi-Stage Dockerfile
Assumes:
App entrypoint: main.py
Dependencies in requirements.txt
# Stage 1: Install dependencies in isolated layer
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip install --user -r requirements.txt
# Stage 2: Production image
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
✅ No dev tools in final image, small size, ready for production.
๐ฉ 2. Node.js (Express) – Multi-Stage Dockerfile
Assumes:
app.js
as entry
Build step not needed (i.e., no TypeScript or Webpack)
# Stage 1: Build dependencies
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
# Stage 2: Production image
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
✅ Final image only contains app and modules — not cache or dev dependencies.
☕ 3. Java (Spring Boot) – Multi-Stage Dockerfile
Assumes:
JAR file built via Maven/Gradle
Entry JAR: target/app.jar
# Stage 1: Build app with Maven
FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: Slim runtime image
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
✅ Uses only JRE in runtime stage — reduces size by 60–70% compared to fat images.
๐ RUN vs CMD in Docker
Feature | RUN | CMD |
---|---|---|
Purpose | Executes at build time | Executes at container runtime |
Used For | Installing packages, configuring files | Running the app or setting default command |
Effect | Creates a new image layer | Does not create a layer in the image |
Output | Changes the image (e.g., files installed) | Sets the container’s default execution |
Runs when | During docker build |
During docker run |
Override | Cannot override in docker run |
Can be overridden in docker run |
๐งช Example
✅ RUN
RUN apt-get update && apt-get install -y curl
This installs curl at image build time. The result is saved in the image.
✅ CMD
CMD ["python", "main.py"]
This runs the app when the container starts (e.g., docker run myimage
).
You can override CMD like:
docker run myimage python other_script.py
๐ Can You Use Both?
Yes — very common.
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
RUN: Install deps once into image.
CMD: Start the app when container runs.
You want to...
Use | RUN | CMD |
---|---|---|
Install software, copy files, or prep image | RUN | |
Set the default command to run when container starts | CMD |
No comments:
Post a Comment