داکر برای Production: راهنمای کامل کانتینریسازی اپلیکیشنهای وب
داکر شیوهٔ deploy کردن اپلیکیشنها را متحول کرده است. این راهنمای جامع همهچیز را پوشش میدهد؛ از کانتینریسازی مقدماتی گرفته تا استقرارهای آمادهٔ production، همراه با بهترین شیوههای امنیتی و استراتژیهای ارکستراسیون.
آشنایی با معماری داکر
پیش از پرداختن به پیکربندیهای production، لازم است اجزای اصلی داکر را بشناسید:
- Docker Engine - محیط اجرایی (runtime) که کانتینرها را میسازد و اجرا میکند
- Images - قالبهای فقطخواندنی که کد اپلیکیشن و وابستگیهای آن را در بر دارند
- Containers - نمونههای در حال اجرای ایمیجها
- Volumes - ذخیرهسازی ماندگار دادهها
- Networks - ارتباط میان کانتینرها
<Callout type="info"> کانتینرها کرنل سیستمعامل میزبان را بهاشتراک میگذارند؛ به همین دلیل در مقایسه با ماشینهای مجازی بسیار سبکترند و در عین حال ایزولهسازی فرایندها را نیز فراهم میکنند. </Callout>
بیلدهای چندمرحلهای برای Production
بیلدهای چندمرحلهای (multi-stage) ایمیجهای production کوچکتر و امنتری تولید میکنند:
اپلیکیشن Node.js
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy only necessary files
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER nextjs
EXPOSE 3000
CMD ["node", "dist/main.js"]اپلیکیشن PHP/Laravel
# Stage 1: Composer dependencies
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-scripts \
--no-autoloader \
--prefer-dist
COPY . .
RUN composer dump-autoload --optimize
# Stage 2: Frontend assets
FROM node:20-alpine AS frontend
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: Production image
FROM php:8.3-fpm-alpine AS production
# Install PHP extensions
RUN apk add --no-cache \
libpng-dev \
libzip-dev \
&& docker-php-ext-install \
pdo_mysql \
gd \
zip \
opcache
# Configure OPcache for production
RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.memory_consumption=256" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.max_accelerated_files=20000" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/opcache.ini
WORKDIR /var/www/html
# Copy application
COPY --from=vendor /app/vendor ./vendor
COPY --from=frontend /app/public/build ./public/build
COPY . .
# Set permissions
RUN chown -R www-data:www-data storage bootstrap/cache
USER www-data
EXPOSE 9000
CMD ["php-fpm"]<Note title="نکتهٔ امنیتی"> در production همیشه کانتینرها را با کاربری غیر از root اجرا کنید. این کار آسیب احتمالی ناشی از container escape را محدود میکند. </Note>
Docker Compose برای محیط توسعه
یک محیط توسعهٔ کامل بسازید:
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
ports:
- "5432:5432"
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
ports:
- "6379:6379"
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
depends_on:
- app
volumes:
postgres_data:
redis_data:Docker Compose برای Production
# docker-compose.prod.yml
version: '3.8'
services:
app:
image: ${REGISTRY}/myapp:${TAG:-latest}
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- app-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.prod.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- app-network
networks:
app-network:
driver: overlayبهترین شیوههای امنیتی
اسکن ایمیجها برای یافتن آسیبپذیریها
# Using Docker Scout
docker scout cves myapp:latest
# Using Trivy
trivy image myapp:latest
# Using Snyk
snyk container test myapp:latestشیوههای امن در نوشتن Dockerfile
# Use specific version tags, not 'latest'
FROM node:20.10.0-alpine3.18
# Don't store secrets in images
# Use build arguments for build-time secrets
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc \
&& npm ci \
&& rm .npmrc
# Use COPY instead of ADD (ADD can fetch remote URLs)
COPY package*.json ./
# Set proper file permissions
RUN chmod -R 755 /app
# Use read-only root filesystem where possible
# (configure in docker-compose or runtime)
# Don't run as root
USER node
# Use HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:3000/health || exit 1امنیت در زمان اجرا
# docker-compose.secure.yml
services:
app:
image: myapp:latest
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /var/run
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICEشبکهبندی کانتینرها
شبکههای Bridge سفارشی
version: '3.8'
services:
frontend:
networks:
- frontend-network
- backend-network
api:
networks:
- backend-network
- database-network
database:
networks:
- database-network
networks:
frontend-network:
driver: bridge
backend-network:
driver: bridge
internal: true # No external access
database-network:
driver: bridge
internal: trueلاگگیری و مانیتورینگ
لاگگیری متمرکز با ELK Stack
version: '3.8'
services:
app:
logging:
driver: gelf
options:
gelf-address: "udp://logstash:12201"
tag: "myapp"
elasticsearch:
image: elasticsearch:8.11.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
logstash:
image: logstash:8.11.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
ports:
- "12201:12201/udp"
kibana:
image: kibana:8.11.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
volumes:
elasticsearch_data:متریکهای Prometheus
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'docker'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'app'
static_configs:
- targets: ['app:3000']
metrics_path: '/metrics'یکپارچهسازی با CI/CD
پایپلاین GitHub Actions
# .github/workflows/docker.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /opt/myapp
docker compose pull
docker compose up -d --remove-orphans
docker image prune -fبهینهسازی عملکرد
بهینهسازی cache در بیلد
# Order layers from least to most frequently changed
FROM node:20-alpine
WORKDIR /app
# System dependencies (rarely change)
RUN apk add --no-cache dumb-init
# Package files (change occasionally)
COPY package*.json ./
RUN npm ci --only=production
# Application code (changes frequently)
COPY . .
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/main.js"]محدودیتهای منابع
services:
app:
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512Mجمعبندی
داکر امکان استقرار یکنواخت و تکرارپذیر را در محیطهای مختلف فراهم میکند. با رعایت این بهترین شیوههای production — بیلدهای چندمرحلهای، سختسازی امنیتی، شبکهبندی درست و یکپارچهسازی با CI/CD — میتوانید اپلیکیشنهای کانتینری مقاوم و آمادهٔ مقیاسپذیری بسازید.
نکات کلیدی:
- برای داشتن ایمیجهای کوچکتر و امنتر از بیلدهای چندمرحلهای استفاده کنید
- در production هرگز کانتینرها را با کاربر root اجرا نکنید
- health check و محدودیتهای منابع را پیادهسازی کنید
- ایمیجها را بهطور منظم از نظر آسیبپذیری اسکن کنید
- از لاگگیری و مانیتورینگ مناسب استفاده کنید
این یادداشت ترجمهٔ فارسی نوشتهٔ خودم است — نسخهٔ اصلی (انگلیسی) در dev.to