Docker Best Practices cho Production
Tối ưu hóa Docker images và containers cho môi trường production. Security, performance, và reliability best practices
Docker Best Practices cho Production
Docker đã revolutionize cách chúng ta deploy applications. Nhưng để run Docker trong production an toàn và hiệu quả, bạn cần follow best practices.
1. Multi-Stage Builds
Multi-stage builds giúp giảm image size đáng kể.
Bad Practice
# ❌ Image rất lớn vì chứa build tools
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["npm", "start"]
# Image size: 1.2 GBBest Practice
# ✅ Multi-stage build
# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
# Image size: 150 MB (8x smaller!)2. Use Specific Tags
Bad Practice
# ❌ "latest" tag không predictable
FROM node:latestBest Practice
# ✅ Specific version tag
FROM node:18.17.1-alpine3.18
# Hoặc sử dụng digest cho immutability
FROM node@sha256:abc123...3. Optimize Layer Caching
Docker caches layers. Optimize để maximize cache hits.
Bad Practice
# ❌ Copy all files trước khi install dependencies
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
# Mỗi lần code change, phải reinstall tất cả dependencies!Best Practice
# ✅ Copy package files trước, tận dụng cache
FROM node:18-alpine
WORKDIR /app
# Copy dependency files first
COPY package*.json ./
RUN npm ci --only=production
# Then copy application code
COPY . .
# Code changes không invalidate dependency layer!4. Minimize Image Size
Choose Slim Base Images
# Image size comparison:
FROM node:18 # 996 MB
FROM node:18-slim # 234 MB (4.2x smaller)
FROM node:18-alpine # 171 MB (5.8x smaller) ✅Remove Unnecessary Files
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.vscode
coverage/
.DS_Store
*.logClean Up in Same Layer
# ❌ Bad: Cleanup in different layer
RUN apt-get update
RUN apt-get install -y wget
RUN wget http://example.com/file
RUN rm -rf /var/lib/apt/lists/* # Không giảm size!
# ✅ Good: Cleanup trong cùng layer
RUN apt-get update && \
apt-get install -y --no-install-recommends wget && \
wget http://example.com/file && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*5. Security Best Practices
Don't Run as Root
# ❌ Bad: Running as root
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["node", "app.js"]
# ✅ Good: Create and use non-root user
FROM node:18-alpine
WORKDIR /app
# Create user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
COPY --chown=nodejs:nodejs . .
USER nodejs
CMD ["node", "app.js"]Scan for Vulnerabilities
# Scan image for vulnerabilities
docker scan myimage:latest
# Use Trivy
trivy image myimage:latest
# Use Snyk
snyk container test myimage:latestUse Read-Only Filesystem
# Make filesystem read-only
FROM node:18-alpine
WORKDIR /app
COPY . .
# Use tmpfs for writable directories
VOLUME ["/tmp"]
USER node
CMD ["node", "app.js"]# Run container with read-only filesystem
docker run --read-only --tmpfs /tmp myimage:latest6. Health Checks
Add Dockerfile HEALTHCHECK
FROM node:18-alpine
WORKDIR /app
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["node", "app.js"]// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
};
const request = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', () => process.exit(1));
request.end();7. Environment Variables
Bad Practice
# ❌ Hardcode secrets
FROM node:18-alpine
ENV DATABASE_PASSWORD=mysecretpasswordBest Practice
# ✅ Use ARG và ENV properly
FROM node:18-alpine
# Build-time variable
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
# Runtime will override from docker-compose or -e flag
ENV DATABASE_URL=
ENV API_KEY=
CMD ["node", "app.js"]# Pass secrets at runtime
docker run -e DATABASE_PASSWORD=secret myimage
# Or use Docker secrets
docker secret create db_password password.txt
docker service create --secret db_password myimage8. Logging Best Practices
Log to STDOUT/STDERR
FROM node:18-alpine
WORKDIR /app
COPY . .
# Don't write logs to files inside container
# Let Docker handle log collection
CMD ["node", "app.js"]// app.js
// ✅ Log to stdout
console.log('Application started');
console.error('Error occurred');
// ❌ Don't write to files
// fs.appendFileSync('/var/log/app.log', 'message');Configure Log Drivers
# docker-compose.yml
services:
app:
image: myapp:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"9. Resource Limits
Set Memory và CPU Limits
# docker-compose.yml
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M# Docker run
docker run -m 512m --cpus="0.5" myimage10. Complete Production Example
Dockerfile
# Multi-stage build for Node.js app
FROM node:18.17.1-alpine3.18 AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# Build application
COPY . .
RUN npm run build
# Production stage
FROM node:18.17.1-alpine3.18
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy built application
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs package*.json ./
# Set environment
ENV NODE_ENV=production
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD node healthcheck.js || exit 1
# Switch to non-root user
USER nodejs
EXPOSE 3000
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
image: myapp:1.0.0
container_name: myapp
restart: unless-stopped
# Environment variables
environment:
- NODE_ENV=production
- PORT=3000
# Secrets (use Docker secrets in production)
env_file:
- .env.production
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
# Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Network
networks:
- app-network
# Ports
ports:
- "3000:3000"
# Security
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
networks:
app-network:
driver: bridge.dockerignore
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Testing
coverage
.nyc_output
test
# Development
.vscode
.idea
.git
.gitignore
# Environment
.env
.env.local
.env.*.local
# Build
dist
build
# Documentation
README.md
CHANGELOG.md
docs
# Misc
.DS_Store
*.log
11. Monitoring và Observability
Export Metrics
// Prometheus metrics
const prometheus = require('prom-client');
const register = new prometheus.Registry();
prometheus.collectDefaultMetrics({ register });
app.get('/metrics', (req, res) => {
res.set('Content-Type', register.contentType);
res.end(register.metrics());
});Container Stats
# Monitor container resources
docker stats
# Get detailed info
docker inspect mycontainer12. CI/CD Integration
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: myregistry/myapp
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ steps.meta.outputs.tags }}
severity: 'CRITICAL,HIGH'Checklist
Trước khi deploy lên production:
- Multi-stage build để minimize size
- Specific version tags (không dùng
latest) - Non-root user
- Health checks configured
- Resource limits set
- Logging to stdout/stderr
- Secrets không hardcode
- Image scanned for vulnerabilities
- .dockerignore configured
- Read-only filesystem where possible
- Proper signal handling (dumb-init)
- Monitoring và metrics exposed
Kết luận
Docker trong production requires discipline và attention to detail:
- Security first: Non-root, scan vulnerabilities, secrets management
- Optimize: Multi-stage builds, layer caching, minimal images
- Reliability: Health checks, resource limits, proper logging
- Observability: Metrics, logs, monitoring
Follow những best practices này giúp Docker containers chạy stable, secure, và efficient trong production!
Bài viết liên quan
Git là gì? Các lệnh thường dùng khi đi làm, Github & Gitlab, Source Control trong VSCode, Git Graph, Git Blame, Sourcetree
Tìm hiểu Git từ cơ bản đến nâng cao: các lệnh Git thiết yếu khi đi làm, làm việc với Github/Gitlab, và sử dụng các công cụ Git GUI như VSCode Source Control, Git Graph, Git Blame và Sourcetree.
Câu hỏi phỏng vấn Linux và Operating System cơ bản đến nâng cao
Chuẩn bị cho phỏng vấn vị trí Linux Developer/DevOps/System Engineer với các câu hỏi từ cơ bản đến nâng cao về Linux và Operating System.
Neural Networks từ Zero: Backpropagation
Backpropagation là trái tim của deep learning. Hiểu rõ thuật toán này giúp bạn master được cách neural networks học từ data.