Docker для QA: контейнеры без боли
Docker для QA: контейнеры без боли
Всё, что QA должен знать о Docker — без лишнего DevOps, с реальными задачами

Зачем QA вообще Docker?
Потому что «у меня всё работает» — это не баг-репорт, а крик о помощи.
Вот реальная ситуация. Тестировщик прогоняет автотесты на своей машине — всё зелёное. Пушит в CI — три теста красные. Начинает разбираться: на его машине PostgreSQL 15, а в CI — PostgreSQL 14. На его машине Node.js 20, в CI — 18. На его машине Redis запущен, в CI — нет. Потратил полдня на отладку окружения вместо тестирования.
Если бы тесты запускались в Docker — проблемы бы не было. Контейнер одинаковый везде: на ноутбуке, в CI, у коллеги. Это и есть главная идея.
Docker нужен QA для четырёх вещей:
| # | Зачем | Пример |
|---|---|---|
| 1 | Одинаковое окружение | Локально, в CI, у коллеги — одна и та же версия всего |
| 2 | Быстрый старт зависимостей | Нужна БД для тестов? docker run postgres — 5 секунд |
| 3 | Изоляция | Тест-сьют не ломает вашу систему, а контейнер можно удалить без следа |
| 4 | Воспроизведение багов | «Баг только на production» — подними такой же контейнер и проверь |
Что такое Docker (простыми словами)
Представьте, что вы заказали пиццу. Вам привезли коробку, внутри — готовая пицца. Вам не нужно знать, какая печь на кухне, какая марка муки, какой рецепт теста. Вы открываете коробку — и едите.
Docker работает так же. Вместо того чтобы устанавливать PostgreSQL, Node.js, Redis и ещё 15 зависимостей на свою машину — вы запускаете контейнер, в котором всё это уже есть и настроено. Контейнер — это коробка с готовым приложением.
Три ключевых понятия:
- Image (образ) — рецепт. Описание того, что внутри контейнера. Скачивается один раз.
- Container (контейнер) — работающий экземпляр образа. Как запущенная программа.
- Dockerfile — инструкция для создания образа. Текстовый файл с командами.
Аналогия: образ — это класс, контейнер — это объект (экземпляр класса). Из одного образа можно запустить 10 контейнеров.
Установка Docker
Windows
Скачайте Docker Desktop и установите. После установки в трее появится иконка кита. Если кит счастливый — Docker работает.
macOS
Тоже Docker Desktop. Скачать → перетащить в Applications → запустить.
Linux
sudo apt-get update
sudo apt-get install docker.io docker-compose
sudo usermod -aG docker $USER
После установки перезайдите в систему и проверьте:
docker --version
Если видите что-то вроде Docker version 27.x.x — всё работает.
docker run — запускаем первый контейнер
Начнём с простого. Запустим PostgreSQL:
docker run --name test-db -e POSTGRES_PASSWORD=secret -p 5432:5432 -d postgres:16
Разберём по кусочкам:
| Часть | Что делает |
|---|---|
docker run | Создать и запустить контейнер |
--name test-db | Дать контейнеру имя (чтобы не запоминать ID) |
-e POSTGRES_PASSWORD=secret | Переменная окружения (пароль для БД) |
-p 5432:5432 | Пробросить порт: порт_хоста:порт_контейнера |
-d | Запустить в фоне (detached) |
postgres:16 | Образ и версия |
Через 5 секунд у вас работает PostgreSQL. Подключитесь к нему через DBeaver, pgAdmin или из тестов — localhost:5432, пользователь postgres, пароль secret.
Ловушка: -p 5432:5432 — порт слева это порт на вашей машине. Если 5432 уже занят (у вас локально стоит Postgres), используйте -p 5433:5432. Подключаетесь к localhost:5433, а внутри контейнера всё равно 5432.
Основные команды Docker
Вот минимум, который нужно знать:
Жизненный цикл контейнера
docker run -d --name my-app nginx
docker ps
docker stop my-app
docker start my-app
docker restart my-app
docker rm my-app
docker rm -f my-app
| Команда | Что делает |
|---|---|
docker run | Создать + запустить контейнер |
docker ps | Список запущенных контейнеров |
docker ps -a | Список всех контейнеров (включая остановленные) |
docker stop | Остановить контейнер |
docker start | Запустить остановленный контейнер |
docker rm | Удалить контейнер |
docker rm -f | Принудительно удалить (даже запущенный) |
Работа с образами
docker images
docker pull nginx:latest
docker rmi nginx:latest
| Команда | Что делает |
|---|---|
docker images | Список скачанных образов |
docker pull | Скачать образ |
docker rmi | Удалить образ |
Отладка
docker logs my-app
docker logs -f my-app
docker exec -it my-app bash
docker inspect my-app
| Команда | Что делает |
|---|---|
docker logs | Посмотреть логи контейнера |
docker logs -f | Следить за логами в реальном времени (как tail -f) |
docker exec -it ... bash | Зайти внутрь контейнера |
docker inspect | Подробная информация о контейнере (сеть, порты, переменные) |
Задача с собеседования: «Тесты на CI падают с ошибкой connection refused к базе. Как дебажить?»
Ответ: docker ps — убедиться, что контейнер с БД запущен. docker logs <имя> — проверить, нет ли ошибок при старте. docker inspect <имя> — проверить, на каком порту и в какой сети контейнер. Частая причина: контейнер с приложением и контейнер с БД в разных Docker-сетях.
Dockerfile — создаём свой образ
Для QA это актуально, когда нужно собрать образ с тестами. Например, упаковать Selenium-тесты в контейнер, чтобы запускать их в CI.
Структура
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["pytest", "--tb=short", "-v"]
Разбор:
| Инструкция | Что делает |
|---|---|
FROM | Базовый образ (на чём строим) |
WORKDIR | Рабочая директория внутри контейнера |
COPY | Копировать файлы с хоста в контейнер |
RUN | Выполнить команду при сборке образа |
CMD | Команда по умолчанию при запуске контейнера |
Собираем и запускаем
docker build -t my-tests .
docker run my-tests
-t my-tests — даём образу тег (имя). Точка . — контекст сборки (текущая папка, где лежит Dockerfile).
Частые ошибки в Dockerfile
1. COPY перед установкой зависимостей:
# ❌ Плохо — каждое изменение кода пересобирает зависимости
COPY . .
RUN pip install -r requirements.txt
# ✅ Хорошо — зависимости кэшируются
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
Docker кэширует слои. Если requirements.txt не менялся — pip install не будет выполняться заново. Но если сначала скопировать весь код, любое изменение в одном файле инвалидирует кэш.
2. Использование latest вместо конкретной версии:
# ❌ Сегодня Python 3.12, завтра 3.13 — тесты сломались
FROM python:latest
# ✅ Фиксированная версия
FROM python:3.12-slim
Docker Compose — поднимаем окружение целиком
docker run — это один контейнер. Но для тестирования обычно нужно несколько: база, API, кэш, очередь. Запускать каждый руками — мучение. Docker Compose позволяет описать всё в одном файле и поднять одной командой.
Пример: API + PostgreSQL + Redis
services:
db:
image: postgres:16
environment:
POSTGRES_DB: testdb
POSTGRES_USER: tester
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tester -d testdb"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
api:
image: myapp/api:latest
ports:
- "8080:8080"
environment:
DATABASE_URL: postgresql://tester:secret@db:5432/testdb
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
Обратите внимание на несколько вещей:
-
db,redis,api— имена сервисов. Они же работают как DNS-имена внутри Docker-сети. API обращается к базе по адресуdb:5432, а неlocalhost:5432. -
depends_on— порядок запуска. API запустится только после того, как БД станет healthy (пройдёт healthcheck). -
healthcheck— проверка, что сервис действительно готов принимать соединения. Без негоdepends_onпросто ждёт старта контейнера, а не готовности сервиса.
Команды Docker Compose
docker compose up -d
docker compose ps
docker compose logs api
docker compose logs -f
docker compose down
docker compose down -v
| Команда | Что делает |
|---|---|
up -d | Поднять всё в фоне |
ps | Статус всех сервисов |
logs <сервис> | Логи конкретного сервиса |
down | Остановить и удалить контейнеры |
down -v | То же + удалить volumes (данные) |
Ловушка: docker compose down не удаляет volumes. Если тесты записали мусор в БД — при следующем up данные останутся. Используйте down -v для чистого старта.
Volumes — данные, которые переживут контейнер
Контейнер — штука одноразовая. Удалили контейнер — данные внутри пропали. Volumes решают эту проблему.
services:
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
db-data:
Здесь две разных привязки:
db-data:/var/lib/...— именованный volume. Docker управляет им сам. Данные БД сохраняются между перезапусками../init.sql:/docker-entrypoint-initdb.d/init.sql— bind mount. Файл с хоста монтируется внутрь контейнера. PostgreSQL автоматически выполнит SQL-файлы изdocker-entrypoint-initdb.dпри первом запуске.
Когда что использовать:
| Тип | Когда |
|---|---|
| Named volume | Данные БД, кэш — то, что Docker управляет сам |
| Bind mount | Конфиги, init-скрипты, исходный код — то, что вы редактируете на хосте |
Docker для тестов: практические сценарии
Сценарий 1: Тестовая БД с начальными данными
Создайте файл init.sql:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO users (name, email) VALUES
('Atajan', 'atajan@test.com'),
('Maria', 'maria@test.com'),
('John', 'john@test.com');
И docker-compose.yml:
services:
test-db:
image: postgres:16
environment:
POSTGRES_DB: testdb
POSTGRES_USER: tester
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
docker compose up -d
Через 5 секунд у вас БД с тестовыми данными. Подключаетесь из тестов к localhost:5432 — и работаете. После тестов — docker compose down -v — и всё чисто.
Сценарий 2: Selenium-тесты в контейнере
services:
chrome:
image: selenium/standalone-chrome:latest
ports:
- "4444:4444"
- "7900:7900"
shm_size: "2g"
tests:
build: .
depends_on:
- chrome
environment:
SELENIUM_URL: http://chrome:4444/wd/hub
shm_size: "2g" — важный нюанс. Chrome в контейнере использует shared memory, и стандартных 64MB не хватает. Без этого параметра тесты будут падать с непонятными ошибками. Порт 7900 — VNC, можно подключиться и наблюдать, что делает браузер.
Сценарий 3: Полный тестовый стенд
services:
db:
image: postgres:16
environment:
POSTGRES_DB: appdb
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 5s
timeout: 3s
retries: 5
api:
build: ./backend
ports:
- "8080:8080"
environment:
DATABASE_URL: postgresql://app:secret@db:5432/appdb
depends_on:
db:
condition: service_healthy
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
API_URL: http://api:8080
tests:
build: ./tests
depends_on:
- api
- frontend
environment:
BASE_URL: http://frontend:3000
API_URL: http://api:8080
Одна команда docker compose up --abort-on-container-exit tests — и весь стенд поднимается, тесты прогоняются, всё останавливается. Флаг --abort-on-container-exit завершает все сервисы, когда контейнер tests завершится.
Networking — почему контейнеры (не) видят друг друга
Это самый частый источник проблем. Разберёмся раз и навсегда.
Правило 1: Контейнеры в одном docker-compose.yml видят друг друга по имени сервиса. API обращается к базе по адресу db:5432, не localhost:5432.
Правило 2: localhost внутри контейнера — это сам контейнер, не ваша машина. Если API в контейнере обращается к localhost:5432 — он ищет Postgres внутри себя, а не в соседнем контейнере.
Правило 3: Порты через -p нужны только для доступа с хоста. Контейнеры между собой общаются по внутренней сети без проброса портов.
┌─────────────────────────────────────────┐
│ Docker Network │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ api │───5432──│ db │ │
│ │ :8080 │ │ :5432 │ │
│ └────┬────┘ └─────────┘ │
│ │ │
└───────┼─────────────────────────────────┘
│ -p 8080:8080
│
┌────┴────┐
│ Host │ ← ваш ноутбук
│ :8080 │
└─────────┘
Задача с собеседования: «Тесты подключаются к БД по localhost:5432, но получают connection refused. Контейнер с БД запущен. В чём проблема?»
Ответ: если тесты тоже в контейнере — нужно использовать имя сервиса (db:5432), а не localhost. Если тесты на хосте — нужно убедиться, что порт проброшен (-p 5432:5432).
Переменные окружения и .env
Не хардкодьте пароли и URL-ы в docker-compose.yml. Используйте .env:
# .env
POSTGRES_USER=tester
POSTGRES_PASSWORD=secret
POSTGRES_DB=testdb
API_PORT=8080
services:
db:
image: postgres:16
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
ports:
- "${DB_PORT:-5432}:5432"
${DB_PORT:-5432} — если DB_PORT не задан, используется 5432. Удобно для переопределения на CI.
Docker Compose автоматически подхватывает файл .env из текущей директории. Не нужно ничего дополнительно указывать.
Docker в CI/CD
На CI Docker используется постоянно. Типичный пайплайн:
# .github/workflows/tests.yml (GitHub Actions)
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start services
run: docker compose up -d
- name: Wait for DB
run: |
until docker compose exec db pg_isready -U tester; do
sleep 2
done
- name: Run tests
run: docker compose run tests
- name: Cleanup
if: always()
run: docker compose down -v
Ключевые моменты:
if: always()в cleanup — контейнеры будут остановлены даже если тесты упали- Ожидание готовности БД —
depends_onне гарантирует, что Postgres принимает соединения docker compose run testsзапускает одноразовый контейнер — отработал и остановился
Задачи с реальных собеседований
Задача 1: «У меня локально работает, а в CI нет»
Тесты используют API на localhost:8080. Локально всё работает. В CI (GitHub Actions) — connection refused. Docker Compose одинаковый. В чём может быть проблема?
Ответ: локально тесты запускаются на хосте и обращаются к порту, проброшенному через -p. В CI тесты, скорее всего, запускаются в контейнере — нужно использовать имя сервиса (api:8080) вместо localhost:8080. Либо запускать тесты через docker compose run, где сеть общая.
Задача 2: «Тесты влияют друг на друга»
Тесты по отдельности зелёные, а вместе — падают. Один тест создаёт пользователя с email test@test.com, другой тоже. При последовательном запуске второй ловит unique constraint violation. Как исправить с помощью Docker?
Ответ: поднимать чистую БД перед каждым набором тестов. docker compose down -v && docker compose up -d перед запуском. Или использовать транзакции — каждый тест оборачивается в транзакцию и делает ROLLBACK в конце. Docker-решение более «грубое», но надёжное.
Задача 3: «Контейнер стартует, но сервис не отвечает»
docker compose up -dпрошёл успешно,docker compose psпоказывает все контейнеры running. Но API возвращает 502. Как дебажить?
Ответ:
docker compose logs api— посмотреть логи APIdocker compose logs db— может, БД не запустиласьdocker compose exec api curl localhost:8080/health— проверить, что API отвечает изнутри- Проверить healthcheck — контейнер может быть running, но не healthy
docker inspect— проверить сеть и порты
Задача 4: «Объясни, что делает этот docker-compose.yml»
Интервьюер показывает файл и просит объяснить каждую строку. Частый формат на собеседованиях — проверяют, понимаете ли вы реально или заучили команды.
Для этого нужно понимать:
- Что такое
services,volumes,networks - Зачем
depends_onи его ограничения - Разницу между
buildиimage - Что делает
healthcheck - Зачем
-vвdocker compose down
Задача 5: «Напиши docker-compose.yml для тестового окружения»
Нужно: PostgreSQL, Redis, бэкенд (образ
myapp/api:v2), запуск тестов (папка./tests, Dockerfile уже есть). Тесты должны запускаться после готовности API и БД.
services:
db:
image: postgres:16
environment:
POSTGRES_DB: testdb
POSTGRES_USER: tester
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tester -d testdb"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
api:
image: myapp/api:v2
environment:
DATABASE_URL: postgresql://tester:secret@db:5432/testdb
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 5
tests:
build: ./tests
environment:
API_URL: http://api:8080
DB_URL: postgresql://tester:secret@db:5432/testdb
depends_on:
api:
condition: service_healthy
5 ловушек, на которых валятся кандидаты
1. localhost внутри контейнера ≠ ваша машина.
Внутри контейнера localhost — это сам контейнер. Чтобы достучаться до соседнего контейнера, используйте имя сервиса. Чтобы достучаться до хоста — host.docker.internal (Docker Desktop) или --network host.
2. depends_on не ждёт готовности сервиса.
depends_on ждёт только запуска контейнера, а не готовности приложения внутри. PostgreSQL может стартовать 10 секунд, а depends_on отпустит зависимый контейнер сразу. Используйте condition: service_healthy + healthcheck.
3. Данные в контейнере — временные.
Удалили контейнер — потеряли данные. Для сохранения используйте volumes. Для тестов это обычно плюс (чистый старт), но для тестовых БД с миграциями — нужно понимать.
4. Разница между docker compose down и docker compose down -v.
Без -v volumes сохраняются. С -v — удаляются. Если тесты засорили БД, а вы забыли -v — следующий прогон может упасть на грязных данных.
5. Разница между CMD и ENTRYPOINT в Dockerfile.
CMD — можно переопределить при docker run. ENTRYPOINT — нет (точнее, сложнее). Для тестов обычно CMD — чтобы можно было запустить конкретный тест: docker run my-tests pytest tests/test_login.py.
Чек-лист: что знать перед собеседованием
- Концепции: image vs container, Dockerfile, Docker Compose
- Команды:
run,ps,stop,rm,logs,exec,build - Dockerfile: FROM, COPY, RUN, CMD, WORKDIR — и порядок слоёв для кэширования
- Docker Compose: services, ports, environment, volumes, depends_on, healthcheck
- Networking: почему
localhostне работает между контейнерами, имена сервисов как DNS - Volumes: named volumes vs bind mounts, зачем
-vвdown - CI/CD: как Docker решает проблему «у меня работает», ожидание готовности сервисов
- Отладка:
logs,exec,inspect— три главных инструмента
Этого хватит для 90% QA-собеседований. Не нужно знать Kubernetes, Docker Swarm или multi-stage builds — это уже DevOps-территория.
Полезные ресурсы
- Docker Getting Started — официальный туториал. Хорошо структурирован, с примерами.
- Play with Docker — браузерная песочница. Не нужно ничего устанавливать — Docker прямо в браузере.
- Docker Compose documentation — справочник по docker-compose.yml.
- Awesome Docker — курированный список ресурсов.
Курс по тестированию с практическими заданиями — бесплатно на annayev.com (English, Русский, Türkçe).
Ставьте плюс, если было полезно. Используете Docker в повседневной работе тестировщика? Расскажите в комментариях, какие сценарии у вас самые частые.