داکر زمانی به تست کمک می‌کند که «محیط» را از یک دستور مبهم به یک قرارداد قابل بازتولید تبدیل کند. اگر فقط docker compose up -d بزنیم و چند ثانیه بعد تست را شروع کنیم، هنوز نمی‌دانیم Database آماده بوده، Migration اجرا شده، کدام Image واقعاً بالا آمده یا داده اجرای قبلی باقی مانده است. نتیجه ممکن است روی لپ‌تاپ سبز و در CI قرمز شود؛ این بار با لایه‌ای از Container روی همان ابهام قدیمی.

در این راهنما، یک الگوی عملی برای محیط تست با Docker Compose می‌سازیم: Image نسخه‌دار، PostgreSQL دارای healthcheck، Migration یک‌باره، App دارای readiness، Test Runner با exit code واقعی، شبکه بدون Port عمومی، Secret فایل‌محور، Artifact شکست و teardown تضمین‌شده. اگر ابتدا باید portfolio محیط‌ها و مرز Fit-for-Purpose را تعیین کنید، مقاله مدیریت محیط تست مکمل این آموزش اجرایی است.

پاسخ کوتاه: Docker برای بسته‌بندی Process و وابستگی‌های آن در Image و اجرای نسبتاً ایزوله Container مناسب است. Docker Compose چند Service، Network، Secret و ترتیب وابستگی را تعریف می‌کند. این ابزارها تکرارپذیری را بهتر می‌کنند، اما خودبه‌خود Production parity، امنیت کامل، داده تمیز یا تست غیر Flaky نمی‌سازند. هویت Image، config، architecture، kernel، resource، data و dependency باید در هر Run ثبت و کنترل شود.

داکر در محیط تست دقیقاً چه مسئله‌ای را حل می‌کند؟

Image، Container و Compose را از هم جدا کنیم

  • Dockerfile: دستور ساخت Image است؛ نتیجه اجرای آن به base image، dependency registry، build context، architecture و cache نیز وابسته است.
  • Image: بسته لایه‌ای و immutable از فایل‌ها، runtime، dependency و metadata است. tag هویت قطعی نیست؛ digest دقیق‌تر است.
  • Container: نمونه در حال اجرای Image با writable layer، process، network، mount، environment و resource policy مشخص است.
  • Compose file: مدل چند Service و منابع مشترک مانند Network، Volume، Config و Secret است.
  • Compose project: اجرای نام‌دار آن مدل است. Project name یکتا، resourceهای Runهای موازی را از هم جدا می‌کند.

تعریف رسمی Docker Container آن را یک Process ایزوله با فایل‌های لازم توصیف می‌کند. Container برخلاف VM کرنل مستقل کامل ندارد و روی macOS/Windows معمولاً Docker Desktop یک Linux VM را میان Host و Linux Container قرار می‌دهد. پس «روی Docker اجرا شد» به معنی شبیه‌سازی هر سیستم‌عامل یا سخت‌افزار نیست.

Consistency با Identity ساخته می‌شود، نه فقط Dockerfile

دو تیم می‌توانند Dockerfile یکسان را در دو روز متفاوت Build کنند و خروجی متفاوت بگیرند؛ چون tag پایه جابه‌جا شده، package repository نسخه تازه داده یا platform متفاوت بوده است. برای محیط تست قابل بازتولید حداقل این موارد را ثبت کنید:

  • Git commit و hash مربوط به build context؛
  • نام Image و manifest digest، نه فقط latest یا 17؛
  • معماری مانند linux/amd64 یا linux/arm64؛
  • Docker Engine/Desktop، Compose و BuildKit version؛
  • Compose config نهایی پس از merge و interpolation؛
  • Secret reference، feature flag، locale/timezone و data seed؛
  • resource limits و mode وابستگی‌های بیرونی.

Isolation طیفی است، نه Boolean

Namespace و cgroup جداسازی مهمی ایجاد می‌کنند، ولی Containerها kernel و Docker host را شریک‌اند. Bind mount، Docker socket، privileged، host network، device mount یا capability اضافه می‌تواند مرز را باز کند. حتی روی شبکه پیش‌فرض Compose، همه Serviceهای عضو آن می‌توانند یکدیگر را با نام پیدا کنند. بنابراین Isolation باید در Process، filesystem، network، data، identity و resource جداگانه طراحی و آزموده شود.

کدام تست‌ها از Docker سود می‌برند؟

نوع تست کاربرد مناسب Docker محدودیت مهم
Unit Toolchain و dependency نسخه‌دار در Build stage startup Container نباید feedback تست‌های بسیار سریع را بی‌دلیل کند.
Component اجرای Service با dependencyهای in-memory یا stub Container به‌تنهایی boundary و Oracle را تعریف نمی‌کند.
Integration Database، broker، cache و migration واقعی و موقت readiness، data isolation و cleanup باید صریح باشد.
Contract Broker یا stub نسخه‌دار در pipeline جای verification واقعی Consumer/Provider را نمی‌گیرد.
System/E2E Stack کوچک چند Service برای Journeyهای منتخب هرچه stack بزرگ‌تر شود، تشخیص Failure و هزینه بیشتر می‌شود.
Performance Load generator و SUT قابل بسته‌بندی برای rehearsal Docker Desktop، shared host و limit نامشخص نتیجه Production را نمایندگی نمی‌کند.
Security Image/config scan و محیط آزمایش محدود Container sandbox مجوز تست مخرب یا امنیت کامل ایجاد نمی‌کند.

برای تمرکز این مقاله، Integration Test بهترین مثال است: API، PostgreSQL و Test Runner در یک Project کوتاه‌عمر. مرز Component/System Integration را در راهنمای تست یکپارچه‌سازی جدا کنید تا Compose stack به E2E همه‌چیز تبدیل نشود.

چه زمانی VM یا محیط مدیریت‌شده مناسب‌تر است؟

  • تست kernel، driver، systemd، boot، installer یا policy سطح OS؛
  • سازگاری واقعی Windows/macOS یا نسخه‌های kernel؛
  • آزمون managed serviceهایی که semantics آن‌ها با Image محلی فرق دارد؛
  • Performance benchmark نیازمند topology، storage و network شبیه Production؛
  • کد غیرقابل اعتماد با نیاز به security boundary قوی‌تر.

Container و VM رقیب مطلق نیستند؛ اغلب Containerهای تست داخل Runner VM کوتاه‌عمر اجرا می‌شوند.

معماری مرجع محیط تست Docker

جریان Evidence به‌جای «up و امید»

  1. Resolve: Imageها با digest و Compose config نهایی مشخص می‌شوند.
  2. Create: Project name یکتا، Network و Containerهای همان Run ساخته می‌شوند.
  3. Dependency health: Database باید query پذیر باشد، نه فقط Process آن Running.
  4. Migrate: Schema migration یک job با exit code مستقل است.
  5. Seed: داده نسخه‌دار، deterministic و namespace دار ایجاد می‌شود.
  6. App readiness: endpoint آماده‌بودن وابستگی حیاتی را بررسی می‌کند.
  7. Test: Runner داخل Network با Service name به App وصل می‌شود.
  8. Collect: JUnit، log، config hash و Image identity ذخیره می‌شوند.
  9. Classify: Failure میان Build/Orchestration/Product/Test/Data/Resource/Dependency تفکیک می‌شود.
  10. Teardown: حتی در Failure، Container، Network و Volume همان Project حذف می‌شوند.

چرا Running با Ready فرق دارد؟

Process دیتابیس ممکن است شروع شده باشد ولی recovery یا initialization تمام نشده باشد. مستندات رسمی Startup order در Compose صریح می‌گوید Compose به‌صورت عادی فقط Running را می‌بیند؛ شرط service_healthy همراه healthcheck برای readiness dependency لازم است. sleep 10 نه وضعیت را می‌سنجد و نه روی Runner کندتر/سریع‌تر قابل اعتماد است.

Health، Readiness و Migration یک Oracle نیستند

  • Health دیتابیس: اتصال و query پایه ممکن است؛ صحت همه migrationها را ثابت نمی‌کند.
  • Migration exit code: تغییر Schema اجرا شده؛ readiness App را ثابت نمی‌کند.
  • App readiness: endpoint و dependencyهای حیاتی آماده‌اند؛ صحت Journey کسب‌وکار را ثابت نمی‌کند.
  • Test assertion: Claim محصول را با داده معین می‌سنجد.

Dockerfile مناسب تست؛ Build قابل بررسی

الگوی زیر برای یک API پایتون است. مقدار PYTHON_IMAGE باید tag به‌همراه digest تأییدشده registry سازمان باشد. عمداً default ندارد تا Build شناور به‌طور پنهان انجام نشود.

# syntax=docker/dockerfile:1
ARG PYTHON_IMAGE
FROM ${PYTHON_IMAGE} AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

RUN groupadd --gid 10001 app \
    && useradd --uid 10001 --gid 10001 --create-home app

COPY requirements.lock ./
RUN python -m pip install --no-cache-dir \
    --require-hashes -r requirements.lock

COPY --chown=app:app src/ ./src/
USER 10001:10001

FROM base AS runtime
CMD ["python", "-m", "src.api"]

FROM base AS test
COPY --chown=app:app tests/ ./tests/
CMD ["python", "-m", "pytest", "-q", "tests"]

چرا multi-stage و non-root؟

راهنمای رسمی Build multi-stage، base image کوچک و قابل اعتماد، حذف package غیرضروری، .dockerignore، اجرای non-root و pin با digest را توصیه می‌کند. Stageهای runtime و test می‌توانند base مشترک داشته باشند، ولی ابزارهای تست لازم نیست وارد Image نهایی Production شوند.

Digest چه چیزی را حل می‌کند و چه چیزی را نه؟

  • digest تضمین می‌کند reference همان manifest/image content مورد انتظار را resolve کند؛
  • tag خوانایی و مسیر upgrade می‌دهد، اما mutable است؛
  • digest آسیب‌پذیری یا مناسب‌بودن Image را تضمین نمی‌کند؛ scan، provenance و review جدا لازم‌اند؛
  • برای multi-platform، digest و platform انتخابی را ثبت کنید؛
  • upgrade digest باید Pull Request قابل مرور با rebuild و regression باشد.

.dockerignore حداقلی

.git
.venv
__pycache__
.pytest_cache
artifacts
test/secrets
.env*
*.log

Secret، history repository، Artifact و فایل‌های محلی نباید بی‌دلیل وارد build context شوند. Build context بخشی از سطح افشا و cache key است.

نمونه compose.test.yaml برای Integration Test

این Template چهار Service دارد: db، migrate، app و tests. مقدارهای digest نمونه نیستند؛ پیش از اجرا باید digest تصویب‌شده واقعی را جایگزین کنید. فایل Secret در repository commit نمی‌شود و CI آن را از Secret manager با دسترسی محدود materialize می‌کند.

services:
  db:
    image: "postgres:17@sha256:<approved-postgres-digest>"
    environment:
      POSTGRES_DB: checkout_test
      POSTGRES_USER: test_user
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test:
        ["CMD-SHELL", "pg_isready -U test_user -d checkout_test"]
      interval: 2s
      timeout: 3s
      retries: 20
      start_period: 10s
    networks:
      - test_backend

  migrate:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
      args:
        PYTHON_IMAGE: "python:3.13-slim@sha256:<approved-python-digest>"
    command: ["python", "-m", "src.migrate"]
    environment:
      DB_HOST: db
      DB_NAME: checkout_test
      DB_USER: test_user
      DB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    depends_on:
      db:
        condition: service_healthy
    networks:
      - test_backend
    restart: "no"

  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
      args:
        PYTHON_IMAGE: "python:3.13-slim@sha256:<approved-python-digest>"
    environment:
      APP_ENV: test
      DB_HOST: db
      DB_NAME: checkout_test
      DB_USER: test_user
      DB_PASSWORD_FILE: /run/secrets/db_password
      TZ: Asia/Tehran
    secrets:
      - db_password
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test:
        - CMD
        - python
        - -c
        - >-
          import urllib.request;
          urllib.request.urlopen(
            'http://127.0.0.1:8000/health/ready',
            timeout=2
          )
      interval: 2s
      timeout: 3s
      retries: 20
      start_period: 5s
    expose:
      - "8000"
    networks:
      - test_backend
    read_only: true
    tmpfs:
      - /tmp
    init: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true

  tests:
    build:
      context: .
      dockerfile: Dockerfile
      target: test
      args:
        PYTHON_IMAGE: "python:3.13-slim@sha256:<approved-python-digest>"
    command:
      - python
      - -m
      - pytest
      - -q
      - tests/integration
      - --junitxml=/artifacts/junit.xml
    environment:
      BASE_URL: http://app:8000
      DATA_SEED: checkout-v4
      TZ: Asia/Tehran
    depends_on:
      app:
        condition: service_healthy
    volumes:
      - ./artifacts:/artifacts
    networks:
      - test_backend
    read_only: true
    tmpfs:
      - /tmp
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true

secrets:
  db_password:
    file: ./test/secrets/db_password.txt

networks:
  test_backend:
    internal: true

نکته‌های مهم همین Compose

  • هیچ host port منتشر نشده: Runner با http://app:8000 و DNS داخلی وصل می‌شود؛ collision اجرای موازی و سطح دسترسی Host کمتر است.
  • Network داخلی است: Serviceها default gateway بیرونی ندارند. اگر App باید dependency بیرونی را ببیند، یک adapter کنترل‌شده یا Network جدا با policy صریح بسازید.
  • Database health دارد: شروع App فقط به Running بودن Process متکی نیست.
  • Migration Service مستقل است: Pipeline exit code آن را پیش از App بررسی می‌کند.
  • App health دارد: دستور healthcheck باید واقعاً داخل runtime Image موجود باشد و dependencyهای حیاتی را با timeout محدود بسنجد.
  • Test Runner exit code دارد: نتیجه pytest مستقیماً نتیجه مرحله تست می‌شود.
  • Secret فایل‌محور است: Password داخل Compose یا Environment عمومی hard-code نشده است.
  • App و Test محدود شده‌اند: non-root Image، read-only filesystem، tmpfs، drop capabilities و no-new-privileges لایه‌های دفاعی‌اند؛ نه اثبات امنیت کامل.

Compose به‌طور پیش‌فرض Serviceها را با نامشان در DNS داخلی قابل دسترس می‌کند؛ مستندات Networking در Compose توصیه می‌کند Service name را به‌جای IP پویا استفاده کنید. اگر فقط Containerهای همان Project مصرف‌کننده‌اند، publish کردن Database روی Host ضرورتی ندارد.

اجرای امن و قابل بازتولید در Local یا CI

۱. پیش‌پرواز

docker version
docker compose version
docker compose -f compose.test.yaml config --quiet

خروجی normalized دستور docker compose config را به‌عنوان Artifact یا hash ذخیره کنید، اما پیش از انتشار آن بررسی کنید Secret یا مسیر حساس در interpolation ظاهر نشده باشد. Compose file ورودی قابل اعتماد محسوب می‌شود؛ کد ناشناس می‌تواند bind mount، device یا command خطرناک تعریف کند.

۲. Project name یکتا

TEST_PROJECT_NAME="checkout_$CI_RUN_ID"

در CI، CI_RUN_ID باید برای هر job یکتا و محدود به کاراکترهای معتبر Compose باشد. در Local یک suffix یکتا مانند شناسه Process یا timestamp کنترل‌شده بسازید. نام ثابت باعث اشتراک Network/Container/Volume میان Runهای همزمان می‌شود.

۳. اجرای مرحله‌ای با Gate

set -o errexit -o nounset -o pipefail

cleanup_test_stack() {
  test_status=$?

  docker compose \
    -f compose.test.yaml \
    -p "$TEST_PROJECT_NAME" \
    ps > artifacts/compose-ps.txt || true

  docker compose \
    -f compose.test.yaml \
    -p "$TEST_PROJECT_NAME" \
    logs --no-color > artifacts/compose.log || true

  docker compose \
    -f compose.test.yaml \
    -p "$TEST_PROJECT_NAME" \
    down --volumes --remove-orphans || true

  exit "$test_status"
}

trap cleanup_test_stack EXIT

docker compose \
  -f compose.test.yaml \
  -p "$TEST_PROJECT_NAME" \
  up -d --build --wait --wait-timeout 90 db

docker compose \
  -f compose.test.yaml \
  -p "$TEST_PROJECT_NAME" \
  run --rm migrate

docker compose \
  -f compose.test.yaml \
  -p "$TEST_PROJECT_NAME" \
  up -d --build --wait --wait-timeout 90 app

docker compose \
  -f compose.test.yaml \
  -p "$TEST_PROJECT_NAME" \
  run --rm --no-deps tests

گزینه --wait در مرجع رسمی docker compose up تا Running/Healthy شدن Serviceها صبر می‌کند. اجرای مرحله‌ای عمداً exit code Migration و Test را جدا نگه می‌دارد. اگر migration شکست بخورد، App و Test اجرا نمی‌شوند و trap ابتدا Evidence را جمع، سپس teardown را انجام می‌دهد.

۴. چرا down –volumes مهم است؟

طبق مرجع docker compose down، Volumeها به‌صورت پیش‌فرض حذف نمی‌شوند؛ --volumes برای پاک‌کردن named/anonymous volumeهای همان Project لازم است. --remove-orphans Serviceهای قدیمی همان Project را نیز جمع می‌کند.

هرگز برای cleanup یک Job از docker system prune یا prune سراسری روی Host مشترک استفاده نکنید. این فرمان می‌تواند resource متعلق به پروژه‌ها و کاربران دیگر را حذف کند. teardown باید با Project name دقیق scope شود.

داده تست، Migration و Reset

سه لایه داده

  1. Schema: migration version و checksum؛
  2. Reference data: currency، status، permission و catalog پایه با نسخه؛
  3. Scenario data: کاربر، سفارش و تراکنش namespace دار همان Test/Run.

Seed باید deterministic، idempotent و دارای version باشد. استفاده از dump ناشناخته Production، هم privacy risk دارد و هم Oracle را مبهم می‌کند. راهبرد masking/synthetic/retention را با مدیریت داده تست هماهنگ کنید.

Database تازه برای هر Run یا Transaction rollback؟

  • Container/Database تازه برای Run: isolation قوی‌تر میان Runها، ولی startup و migration پرهزینه‌تر؛
  • Schema/Database جدا برای Worker: تعادل مناسب Parallel، با cleanup دقیق؛
  • Transaction rollback برای Case: سریع، اما رفتار commit، async job و چند connection را کامل نمایندگی نمی‌کند؛
  • Snapshot/template: سریع‌تر برای dataset بزرگ، ولی version و invalidation سخت‌تر.

یک پاسخ جهانی وجود ندارد. تصمیم را با نوع Test، concurrency، حجم data و نیاز مشاهده side effect بگیرید.

Migration را داخل startup مبهم نکنید

اگر هر replica هنگام startup migration بزند، race و lock محتمل است. در محیط نمونه، Migration یک job با exit code و log مستقل است. سپس App بالا می‌آید. برای backward-compatible rollout واقعی ممکن است الگوی دیگری لازم باشد؛ تست محیط باید همان migration contract مورد انتظار Release را ارزیابی کند.

Image identity و Supply Chain

Built once، tested once، promoted by digest

بهترین مسیر این است که CI یک App Image بسازد، همان digest را در Integration/System/Security stage تست کند و همان Artifact را promote کند. Rebuild جدا برای هر Stage می‌تواند dependency یا timestamp متفاوت وارد کند. Dockerfile مشترک کافی نیست؛ Image digest باید در تصمیم Release دیده شود.

Manifest اجرای تست

run_id: checkout-integration-1842
commit_sha: a1b2c3d
compose_project: checkout_1842
compose_config_sha256: "<sha256>"
images:
  app: registry.example/checkout@sha256:<digest>
  postgres: postgres@sha256:<digest>
platform: linux/amd64
docker_engine: "<runtime-version>"
docker_compose: "<runtime-version>"
migration: "20260806_03"
data_seed: checkout-v4
locale: fa-IR
timezone: Asia/Tehran
dependencies:
  payment: fake-v3
  sms: stub-v2

Manifest شامل Secret نیست؛ فقط reference یا version امن آن را نگه می‌دارد. اگر Runner arm64 ولی Release amd64 است، تفاوت را پنهان نکنید. multi-arch manifest می‌تواند برای هر platform محتوای متفاوت resolve کند.

Upgrade policy

  • bot یا owner، digest/base/dependency را در Pull Request جدا به‌روز کند؛
  • SBOM/provenance و scan نتیجه جدید با baseline مقایسه شود؛
  • Build clean و regression منتخب اجرا شود؛
  • rollback به digest قبلی ممکن باشد؛
  • tag latest در مسیر Release یا Test decision استفاده نشود.

تحلیل Image، Dockerfile و IaC به تست runtime محدود نیست؛ تحلیل استاتیک برای QA مرز SAST، dependency، secret و IaC scanning را توضیح می‌دهد.

Network، Port و Dependency Fidelity

Service-to-service بدون Port عمومی

داخل Compose، App به db:5432 و Test به app:8000 وصل می‌شود. localhost در Container به همان Container اشاره دارد، نه Host یا Service دیگر. فقط وقتی انسان یا ابزار Host باید وصل شود Port publish کنید و binding را به 127.0.0.1 محدود یا dynamic کنید.

شبکه داخلی محدودیت دارد

internal: true egress پیش‌فرض آن Network را می‌بندد؛ اما Service متصل به Network دوم می‌تواند از آن مسیر خارج شود. DNS، proxy، host network و bind mountها را نیز ممیزی کنید. برای fault test، dependency را با Stub/Proxy کنترل‌شده جایگزین کنید؛ قطع تصادفی کل Docker host Oracle مناسبی نیست.

نردبان Fidelity برای dependency

  1. Fake in-process برای feedback سریع؛
  2. Stub/Mock service با contract نسخه‌دار؛
  3. Container واقعی همان technology مانند PostgreSQL؛
  4. Sandbox مدیریت‌شده provider؛
  5. تعداد محدود آزمون مجاز روی dependency واقعی.

Contract Test می‌تواند drift میان Stub و Provider را کاهش دهد؛ برای چرخه Broker و deployment gate، مقاله تست قراردادی API با Pact را ببینید.

امنیت محیط تست Docker

Docker daemon سطح اعتماد بالایی دارد

راهنمای امنیت Docker Engine علاوه بر namespace/cgroup، سطح حمله daemon و loopholeهای configuration را محور می‌داند. دسترسی به Docker socket در بسیاری از setupها معادل کنترل گسترده Host است. آن را داخل Test Container mount نکنید مگر معماری آگاهانه، Runner موقت و policy محدود داشته باشید.

Hardening حداقلی

  • Image تأییدشده و pin‌شده؛
  • Process non-root و no-new-privileges؛
  • drop capabilityهای غیرلازم و ممنوعیت privileged؛
  • filesystem read-only و tmpfs فقط برای مسیرهای لازم؛
  • عدم bind mount گسترده source، Host root یا credential directory؛
  • network segmentation و عدم publish بی‌نیاز؛
  • Secret فایل‌محور با grant per-service، نه commit یا log؛
  • CPU/memory/PID limit متناسب با Runner؛
  • Runner کوتاه‌عمر و cleanup scope شده؛
  • review Compose/Dockerfile همانند کد اجرایی.

Secret در Compose

مستندات Compose Secrets Secret را به‌صورت فایل در /run/secrets/<name> و فقط برای Serviceهای مجاز mount می‌کند. این روش احتمال افشای ناخواسته Environment را کاهش می‌دهد، اما فایل منبع Host، permission، CI secret store، log و retention همچنان مسئولیت تیم است. Password تست باید non-production و قابل rotate باشد.

Parallel Testing بدون تداخل

Project name اولین namespace است

هر Worker نام Project مستقل می‌گیرد؛ Compose نام Network و resourceها را با آن scope می‌کند. اما این به‌تنهایی کافی نیست. Artifact directory، test account، Kafka topic، bucket prefix، schema/database و external sandbox identity نیز باید Worker/Run ID داشته باشند.

Port collision را حذف کنید، نه مدیریت دستی

وقتی Test Runner داخل Network است، host port لازم نیست. اگر browser روی Host به App نیاز دارد، dynamic port publish و سپس docker compose port را query کنید. Port ثابت ۵۴۳۲/۸۰۰۰ برای ده Worker collision می‌سازد.

Resource contention را اندازه بگیرید

Containerهای جدا هنوز CPU، memory، disk I/O و daemon را شریک‌اند. Run موازی ممکن است health timeout یا latency را تغییر دهد. resource limit، worker count و p95 startup را ثبت کنید. افزایش Parallelism زمانی موفق است که wall-clock کاهش یابد بدون اینکه infra failure و queue time رشد نامتناسب داشته باشد.

Docker در CI/CD؛ Lane و Quality Gate

Lane سریع Pull Request

  • lint و static checks Dockerfile/Compose؛
  • Unit/Component در Build stage؛
  • یک Integration stack باریک با dependencyهای ضروری؛
  • config validation، Image identity و JUnit Artifact؛
  • timeout و teardown اجباری.

Lane Post-merge

  • Integrationهای گسترده‌تر و چند version dependency ریسک‌محور؛
  • Contract verification؛
  • migration از snapshot نسخه قبلی؛
  • fault/timeout کنترل‌شده؛
  • Image/SBOM/security scan.

Lane Release

  • همان App digest نامزد Release؛
  • محیط مدیریت‌شده یا topology نماینده برای ریسک‌های لازم؛
  • Performance/Resilience با limit و observability مشخص؛
  • Residual risk و dependency mode ثبت‌شده.

برای طراحی Fast/Slow/Event-driven lane و gate، راهنمای تست مداوم در CI/CD را به این setup متصل کنید. Docker وسیله orchestration است؛ تصمیم Release به Evidence ریسک وابسته است.

Performance Testing داخل Docker؛ هشدارهای ضروری

چه چیزی قابل استفاده است؟

بسته‌بندی Load Generator، dependency و collector می‌تواند Setup را repeatable کند. resource limit و image identity به مقایسه Runها کمک می‌کند. با این حال latency و throughput روی laptop، Docker Desktop VM یا shared CI Runner نماینده Production نیست.

برای نتیجه قابل دفاع ثبت کنید

  • Host/VM type، kernel، CPU/memory و architecture؛
  • Container limits و throttling؛
  • storage driver و volume type؛
  • network path و co-location generator/SUT؛
  • workload model، data cardinality و warm-up؛
  • p95/p99، error rate، goodput و resource telemetry.

Load Generator را با SUT روی یک Host اشباع‌شده قرار ندهید مگر اینکه این coupling بخشی از آزمایش باشد. برنامه‌ریزی کامل را در راهنمای تست عملکرد انجام دهید.

عیب‌یابی محیط تست Docker

نشانه علت محتمل بررسی بعدی
Build روی یک Runner فرق دارد tag mutable، platform، cache یا registry متفاوت digest، platform، build args، lockfile و config hash را مقایسه کنید.
App قبل از DB Fail می‌شود Running به‌جای Healthy healthcheck، service_healthy و log initialization.
Migration گاهی race می‌کند migration در چند replica یا startup App job واحد، lock و exit code مستقل.
تست به localhost وصل نمی‌شود Container مقصد خودش را می‌بیند Service name و container port را استفاده کنید.
Run دوم داده قبلی دارد Volume باقی‌مانده یا Project name ثابت down --volumes و resource label/name همان Run.
فقط Parallel Fail می‌شود Port/data/account/topic مشترک یا resource contention namespace همه resourceها و telemetry Host.
Health همیشه Unhealthy است ابزار healthcheck داخل Image نیست یا Oracle غلط است فرمان را داخل Container اجرا و timeout/log را بررسی کنید.
Secret در log دیده می‌شود Environment dump، command echo یا app logging rotation فوری، redaction و file-based secret.
Disk Runner پر می‌شود Image/cache/volume/artifact leak Project-scoped cleanup، retention و owner؛ نه prune سراسری.
macOS با CI Linux فرق دارد Docker Desktop VM، architecture یا filesystem Manifest را diff و همان platform نامزد Release را در CI تست کنید.

Failure taxonomy

  • Build/Supply chain: dependency، registry، digest، compilation یا policy؛
  • Orchestration: Compose config، network، health، migration یا teardown؛
  • Product: رفتار App برخلاف Oracle؛
  • Test: assertion، fixture، timing یا cleanup؛
  • Data: seed، schema، collision یا privacy control؛
  • Resource: CPU/memory/disk/port/daemon؛
  • Dependency: service بیرونی یا Sandbox؛
  • Unknown: Evidence ناکافی.

Retry قبل از طبقه‌بندی، Failure اول را پنهان می‌کند. attempt نخست، log و Manifest حفظ شود؛ Passed-on-retry یک Flaky candidate است.

Anti-patternهای Docker در تست

  • latest everywhere: Run قابل بازتولید نیست.
  • sleep after up: زمان جای readiness را می‌گیرد.
  • همه Serviceها روی یک Network و Port عمومی: سطح دسترسی و collision زیاد می‌شود.
  • bind mount کل repository: Host و Container به‌طور ناخواسته coupling و افشا می‌شوند.
  • mount کردن Docker socket: Test Runner کنترل خطرناک Host می‌گیرد.
  • root/privileged برای رفع Permission: علت permission پنهان و boundary ضعیف می‌شود.
  • Volume مشترک میان Workerها: داده و نتیجه Testها تداخل می‌کند.
  • Migration در startup هر App: race و ownership مبهم می‌شود.
  • Rebuild در هر Stage: Artifact تست‌شده با Artifact Release یکی نیست.
  • Prune سراسری در CI مشترک: resource دیگران حذف می‌شود.
  • Production dump در لپ‌تاپ: privacy و کنترل دسترسی نقض می‌شود.
  • Container = Production parity: تفاوت kernel/resource/network/managed service نادیده می‌ماند.

متریک‌های سالم برای محیط تست کانتینری

Flow و قابلیت بازتولید

  • زمان p50/p95 از Create تا Ready؛
  • درصد Run با Image digest، config hash و data seed کامل؛
  • نرخ Failure به تفکیک taxonomy؛
  • Passed-on-retry و Flaky rate؛
  • زمان تشخیص Owner از Artifact؛
  • تعداد resource leak پس از teardown؛
  • queue time و wall-clock در برابر تعداد Worker؛
  • عمر base image و زمان مانده تا patch تصویب‌شده.

Metricهای گمراه‌کننده

  • تعداد Container به‌عنوان پوشش؛
  • درصد cache hit به‌عنوان کیفیت؛
  • Pass rate بدون Blocked/Infra/Quarantine؛
  • سرعت Local به‌عنوان Performance محصول؛
  • یکسان‌بودن Dockerfile به‌عنوان برابری Environment؛
  • صفرشدن Test Failure با Retry زیاد.

نکات ویژه تیم‌های ایرانی

Registry و dependency دسترس‌پذیر اما کنترل‌شده

قطع یا محدودیت دسترسی registry نباید با Failure محصول اشتباه شود. Imageهای مجاز را در registry/cache سازمانی، با digest، provenance و retention نگه دارید؛ dependency lock و Artifact لازم را پیش از Release آماده کنید. استفاده از mirror باید با مجوز، سیاست امنیتی و verification hash انجام شود.

داده و locale واقعی ایران

  • fa-IR و Asia/Tehran را صریح ثبت کنید؛
  • تومان/ریال، رقم فارسی/عربی/لاتین و rounding را در fixture جدا بسنجید؛
  • تاریخ جلالی/میلادی و timestamp UTC را در boundaryها بررسی کنید؛
  • شماره موبایل، کد ملی و آدرس واقعی مشتری وارد Image/Volume/Artifact نشود؛
  • درگاه، SMS و Push با Fake/Sandbox نسخه‌دار و lane واقعی محدود تست شوند.

Cloud Runner یا On-prem؟

تصمیم فقط قیمت دقیقه نیست: دسترسی registry، data residency، secret store، runner isolation، architecture، egress، support و امکان خروج را بسنجید. برای اجرای Docker در Providerهای ابری و boundary مسئولیت، راهنمای تست ابری را ببینید.

برنامه ۳۰روزه پیاده‌سازی

هفته اول: Baseline و یک Integration

  • یک Service و Database پرریسک انتخاب کنید؛
  • زمان Setup دستی، Flake و failureهای محیطی را baseline بگیرید؛
  • Dockerfile multi-stage و dependency lock بسازید؛
  • health، migration و seed contract را تعریف کنید.

هفته دوم: Compose و Evidence

  • Project name یکتا، network داخلی و Secret per-service؛
  • Test Runner یک‌باره با exit code؛
  • Manifest، JUnit، log و config hash؛
  • trap و teardown با volumes.

هفته سوم: CI و Parallel

  • Runner موقت و timeout؛
  • دو Worker با data/artifact namespace مستقل؛
  • resource limit و telemetry؛
  • failure taxonomy و owner.

هفته چهارم: Hardening و تصمیم Scale

  • digest pin، scan، SBOM/provenance policy؛
  • non-root/read-only/capability/network review؛
  • registry/cache و ایران fallback کنترل‌شده؛
  • مقایسه p95 readiness، Flake، maintenance و lead time با baseline؛
  • تصمیم Adopt/Revise/Stop برای Serviceهای بعدی.

چک‌لیست نهایی محیط تست با Docker Compose

  • نوع Test و Claim آن مشخص است؛ Compose stack به E2E بی‌مرز تبدیل نشده.
  • App و dependency Imageها با digest و platform ثبت شده‌اند.
  • Compose config نهایی validate و hash شده است.
  • Project name، data و Artifact برای هر Run/Worker یکتا است.
  • Database healthcheck و App readiness معنای مشخص دارند.
  • Migration job و exit code مستقل است.
  • Seed deterministic، نسخه‌دار و بدون PII واقعی است.
  • Test Runner داخل Network با Service name وصل می‌شود.
  • Port عمومی فقط در صورت نیاز و محدودشده publish می‌شود.
  • Secret commit یا log نمی‌شود و grant per-service دارد.
  • Process non-root، filesystem محدود و capabilityها حداقلی‌اند.
  • Docker socket، privileged و bind mount گسترده وجود ندارد.
  • JUnit، log، Manifest و Failure نخست نگهداری می‌شوند.
  • teardown در Success/Failure اجرا و Volumeهای همان Project حذف می‌شوند.
  • هیچ prune سراسری روی Host مشترک انجام نمی‌شود.
  • Performance فقط با topology/resource نماینده تفسیر می‌شود.
  • Failure taxonomy، owner، retry و quarantine policy داریم.

سوالات متداول درباره Docker در تست

آیا Docker محیط Development، Test و Production را کاملاً یکسان می‌کند؟

خیر. می‌تواند App Image یکسان را حمل کند، اما runtime config، secret، data، dependency، kernel، architecture، storage، network و resource متفاوت‌اند. شباهت باید در Manifest و Fidelity contract سنجیده شود، نه با وجود یک Dockerfile.

تفاوت Docker Compose و Testcontainers برای تست چیست؟

Compose یک stack چند Service مستقل از زبان را با CLI مدیریت می‌کند؛ کتابخانه‌های Testcontainers lifecycle dependency را از داخل Test code و runner زبان مدیریت می‌کنند. انتخاب به ownership، reuse، نیاز debugging، parallel و CI بستگی دارد. هر دو همچنان به digest، readiness، data و cleanup درست نیاز دارند.

آیا depends_on برای آماده‌شدن Database کافی است؟

نه در حالت ساده. Running شدن Container به معنی آماده‌بودن Service نیست. healthcheck معنی‌دار و شرط service_healthy لازم است؛ Migration و App readiness نیز Gateهای جدا هستند.

برای هر Test Case یک Container جدید بسازیم؟

همیشه نه. برای Testهای کاملاً مستقل ممکن است مفید باشد، اما startup پرهزینه است. می‌توان stack را در سطح Suite نگه داشت و برای Caseها transaction، schema یا namespace جدا ساخت. تصمیم باید isolation مورد نیاز را با duration و concurrency متعادل کند.

چگونه نشت Container و Volume را در CI متوقف کنیم؟

Project name یکتا، label/owner، timeout، trap نهایی و docker compose down --volumes --remove-orphans داشته باشید. پس از Run resourceهای همان Project را audit کنید. prune سراسری روی Host مشترک راه‌حل امنی نیست.

جمع‌بندی: ارزش Docker برای QA از «سبک‌بودن Container» نمی‌آید؛ از Environment contract قابل اندازه‌گیری می‌آید. Image را با digest بشناسید، readiness را با health بسنجید، migration و seed را نسخه‌دار کنید، Test Runner را داخل Network اجرا کنید، Evidence را پیش از teardown بگیرید و cleanup را به Project دقیق محدود کنید. سپس با یک Integration باریک شروع و فقط پس از کاهش واقعی Failure محیطی و زمان تشخیص، الگو را گسترش دهید.

دیدگاهتان را بنویسید