Before Optimization#
A typical Dockerfile for a Node.js application looks like this:
1
2
3
4
5
| FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
|
This image ends up being over 1GB in size. The main reasons for this are:
- Heavy base image
- Development tools included
- Presence of unnecessary files
- Accumulation of cache files
Optimization Techniques#
1. Implement Multi-Stage Builds#
Separate the build and runtime stages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Runtime stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm install --production
CMD ["npm", "start"]
|
2. Use Alpine Linux#
Alpine Linux significantly reduces the base image size.
1
2
3
4
5
| # Bad example: node:18 (~1GB)
FROM node:18
# Good example: node:18-alpine (~120MB)
FROM node:18-alpine
|
3. Include Production Dependencies Only#
Exclude development dependencies.
1
2
3
4
5
| # Bad example
RUN npm install
# Good example
RUN npm ci --only=production
|
4. Remove Unnecessary Files#
Use a .dockerignore file.
node_modules
.git
.vscode
*.log
tests
docs
5. Optimize Layers#
Combine commands to reduce the number of layers.
1
2
3
4
5
6
7
8
9
| # Bad example
RUN apk update
RUN apk add python3
RUN rm -rf /var/cache/apk/*
# Good example
RUN apk update &&
apk add python3 &&
rm -rf /var/cache/apk/*
|
Real-World Use Cases#
Node.js Web Application#
Before optimization:
1
2
3
4
5
| FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
|
Image size: 1.2GB
After optimization:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production &&
npm cache clean --force
CMD ["npm", "start"]
|
Image size: 120MB
Go Application#
Before optimization:
1
2
3
4
5
| FROM golang:1.16
WORKDIR /app
COPY . .
RUN go build -o main .
CMD ["./main"]
|
Image size: 850MB
After optimization:
1
2
3
4
5
6
7
8
9
10
| FROM golang:1.16-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM scratch
COPY --from=builder /app/main .
CMD ["./main"]
|
Image size: 15MB
Python Web Application#
Before optimization:
1
2
3
4
5
6
| FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
|
Image size: 900MB
After optimization:
1
2
3
4
5
6
7
8
9
10
11
12
| FROM python:3.9-alpine AS builder
WORKDIR /app
COPY requirements.txt .
RUN apk add --no-cache gcc musl-dev &&
pip install --user -r requirements.txt
FROM python:3.9-alpine
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
|
Image size: 100MB
Reducing image size provides the following benefits:
- Faster deployment times
- Network bandwidth savings
- Reduced container startup times
- Lower storage costs
- Decreased security vulnerabilities
Image optimization is an ongoing process. With each new release, optimization techniques should be applied and results measured. This leads to reduced operational costs and improved system performance.