Привет, Хабр! Это продолжение серии про QA-собеседования. Уже разобрали тест-дизайнAPI и SecuritySystem Design и SQL. Теперь — Docker.

Если при слове «контейнер» в голове только грузовые суда — эта статья для вас.

Содержание

  1. Зачем QA вообще Docker?

  2. Что такое Docker (простыми словами)

  3. Установка Docker

  4. docker run — запускаем первый контейнер

  5. Основные команды Docker

  6. Dockerfile — создаём свой образ

  7. Docker Compose — поднимаем окружение целиком

  8. Volumes — данные, которые переживут контейнер

  9. Docker для тестов: практические сценарии

  10. Networking — почему контейнеры (не) видят друг друга

  11. Переменные окружения и .env

  12. Docker в CI/CD

  13. Задачи с реальных собеседований

  14. 5 ловушек, на которых валятся кандидаты

  15. Чек-лист перед собеседованием

  16. Полезные ресурсы

1. Зачем QA вообще Docker?

Коротко: Docker нужен QA, чтобы запускать одинаковое окружение локально, в CI и у коллег, быстро поднимать зависимости и воспроизводить баги без «у меня не повторяется».

Потому что «у меня всё работает» — это не баг-репорт, а крик о помощи.

Вот реальная ситуация. Тестировщик прогоняет автотесты на своей машине — всё зелёное. Пушит в 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» — подними такой же контейнер и проверь

2. Что такое Docker (простыми словами)

Коротко: Docker упаковывает приложение и его зависимости в контейнер, который запускается одинаково на любой машине.

2.1 Аналогия

Представьте, что вы заказали пиццу. Вам привезли коробку, внутри — готовая пицца. Вам не нужно знать, какая печь на кухне, какая марка муки, какой рецепт теста. Вы открываете коробку — и едите.

Docker работает так же. Вместо того чтобы устанавливать PostgreSQL, Node.js, Redis и ещё 15 зависимостей на свою машину — вы запускаете контейнер, в котором всё это уже есть и настроено. Контейнер — это коробка с готовым приложением.

2.2 Три ключевых понятия

Понятие

Что это

Аналогия

Image (образ)

Описание того, что внутри контейнера. Скачивается один раз

Рецепт / Класс

Container (контейнер)

Работающий экземпляр образа

Готовое блюдо / Объект

Dockerfile

Текстовый файл с инструкциями для создания образа

Кулинарная книга

Из одного образа можно запустить 10 контейнеров — как из одного класса можно создать 10 объектов.

2.3 Docker vs виртуальная машина

Docker-контейнер

Виртуальная машина

Запуск

Секунды

Минуты

Размер

Мегабайты

Гигабайты

ОС

Общее ядро с хостом

Своя полная ОС

Изоляция

На уровне процессов

Полная

Для QA

Тестовые окружения, CI

Тестирование на другой ОС

Для 95% задач тестировщика Docker — правильный выбор. VM нужна только если тестируете под другую ОС (например, Windows-прил��жение на Mac).

3. Установка Docker

Коротко: Для Windows и macOS чаще всего ставят Docker Desktop, для Linux — пакетами. После установки обязательно проверьте, что запускаются и сам Docker, и тестовый контейнер.

3.1 Windows / macOS

Скачайте Docker Desktop и установите. После установки в трее появится иконка кита. Если кит счастливый — Docker работает.

3.2 Linux

sudo apt-get update
sudo apt-get install docker.io docker-compose
sudo usermod -aG docker $USER

3.3 Проверка

После установки перезайдите в систему и проверьте:

docker --version

Если видите что-то вроде Docker version 27.x.x — всё работает.

docker run hello-world

Если видите Hello from Docker! — контейнеры запускаются. Можно двигаться дальше.

4. docker run — запускаем первый контейнер

Коротко: docker run — базовая команда, которая создаёт контейнер, задаёт ему имя, переменные окружения, порты и сразу запускает его.

4.1 Запуск PostgreSQL

Начнём с того, что нужно каждому тестировщику — поднять базу данных:

docker run --name test-db -e POSTGRES_PASSWORD=secret -p 5432:5432 -d postgres:16

4.2 Разбор команды

Часть

Что делает

docker run

Создать и запустить контейнер

--name test-db

Дать контейнеру имя (чтобы не запоминать ID)

-e POSTGRES_PASSWORD=secret

Переменная окружения (пароль для БД)

-p 5432:5432

Пробросить порт: порт_хоста:порт_контейнера

-d

Запустить в фоне (detached)

postgres:16

Образ и версия

4.3 Результат

Через 5 секунд у вас работает PostgreSQL. Подключитесь к нему через DBeaver, pgAdmin или из тестов:

Параметр

Значение

Host

localhost

Port

5432

User

postgres

Password

secret

⚠️ Ловушка: порт уже занят

-p 5432:5432 — порт слева это порт на вашей машине. Если 5432 уже занят (у вас локально стоит Postgres), используйте -p 5433:5432. Подключаетесь к localhost:5433, а внутри контейнера всё равно 5432.

4.4 Ещё примеры docker run

Что запускаем

Команда

MySQL

docker run --name mysql -e MYSQL_ROOT_PASSWORD=secret -p 3306:3306 -d mysql:8

Redis

docker run --name redis -p 6379:6379 -d redis:7-alpine

MongoDB

docker run --name mongo -p 27017:27017 -d mongo:7

Nginx

docker run --name nginx -p 80:80 -d nginx:latest

Обратите внимание: alpine в redis:7-alpine — это облегчённый образ. Вместо ~100MB получаете ~30MB. Для тестов — идеально.

5. Основные команды Docker

Коротко: Для ежедневной работы QA обычно хватает шести вещей: запустить контейнер, посмотреть список, остановить, удалить, посмотреть логи и зайти внутрь.

5.1 Жизненный цикл контейнера

docker run -d --name my-app nginx
docker ps
docker ps -a
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

Остановить контейнер (SIGTERM → SIGKILL через 10с)

docker start

Запустить остановленный контейнер

docker restart

Перезапустить контейнер

docker rm

Удалить остановленный контейнер

docker rm -f

Принудительно удалить (даже запущенный)

5.2 Работа с образами

docker images
docker pull nginx:latest
docker rmi nginx:latest

Команда

Что делает

docker images

Список скачанных образов

docker pull

Скачать образ из Docker Hub

docker rmi

Удалить образ

5.3 Отладка — три главных инструмента QA

Команда

Что делает

Когда использовать

docker logs my-app

Посмотреть логи контейнера

Контейнер упал или сервис не отвечает

docker logs -f my-app

Следить за логами в реальном времени

Воспроизводите баг и хотите видеть, что происходит

docker exec -it my-app bash

Зайти внутрь контейнера

Проверить файлы, конфиги, переменные окружения

docker inspect my-app

Подробная информация (сеть, порты, volumes)

Контейнеры не видят друг друга

docker logs my-app
docker logs -f my-app
docker exec -it my-app bash
docker inspect my-app

5.4 Очистка — когда диск забился

docker system df
docker system prune
docker system prune -a --volumes

Команда

Что делает

docker system df

Показать, сколько места занимает Docker

docker system prune

Удалить остановленные контейнеры, неиспользуемые сети, повисшие образы

docker system prune -a --volumes

Удалить всё неиспользуемое (осторожно!)

Задача с собеседования: тесты на CI падают с connection refused

«Тесты на CI падают с ошибкой connection refused к базе. Как дебажить?»

Ответ:

  1. docker ps — убедиться, что контейнер с БД запущен

  2. docker logs <имя> — проверить, нет ли ошибок при старте

  3. docker inspect <имя> — проверить, на каком порту и в какой сети контейнер

Частая причина: контейнер с приложением и контейнер с БД в разных Docker-сетях.

6. Dockerfile — создаём свой образ

Коротко: Dockerfile нужен, когда вы хотите не просто скачать готовый контейнер, а собрать собственный образ, например с автотестами и зависимостями проекта.

Для QA это актуально, когда нужно собрать образ с тестами. Например, упаковать Selenium-тесты в контейнер, чтобы запускать их в CI.

6.1 Пример Dockerfile для тестов

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"]

6.2 Разбор инструкций

Инструкция

Что делает

Пример

FROM

Базовый образ (на чём строим)

FROM python:3.12-slim

WORKDIR

Рабочая директория внутри контейнера

WORKDIR /app

COPY

Копировать файлы с хоста в контейнер

COPY . .

RUN

Выполнить команду при сборке образа

RUN pip install -r requirements.txt

CMD

Команда по умолчанию при запуске

CMD ["pytest", "-v"]

ENV

Задать переменную окружения

ENV PYTHONPATH=/app

EXPOSE

Документация: какой порт слушает приложение

EXPOSE 8080

6.3 Сборка и запуск

docker build -t my-tests .
docker run my-tests

-t my-tests — даём образу тег (имя). Точка . — контекст сборки (текущая папка, где лежит Dockerfile).

docker run my-tests pytest tests/test_login.py -v

Можно переопределить CMD и запустить конкретный тест.

6.4 Кэширование слоёв — почему порядок важен

Docker кэширует каждый слой (каждую инструкцию). Если слой не изменился — Docker не пересобирает его и использует готовый результат из кэша.

Что это значит на практике: если вы сначала копируете весь проект, а уже потом ставите зависимости, то любое изменение в коде ломает кэш и заставляет заново выполнять pip install.

Плохо: зависимости пересобираются почти при каждом изменении.

# ❌ Плохо — каждое изменение кода пересобирает зависимости
COPY . .
RUN pip install -r requirements.txt

Хорошо: сначала копируем файл зависимостей, потом ставим их, и только после этого копируем остальной код.

# ✅ Хорошо — зависимости кэшируются отдельно
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

Правило: сначала копируйте то, что меняется редко (зависимости), потом — то, что меняется часто (код).

⚠️ Ловушка: latest-тег
# ❌ Сегодня Python 3.12, завтра 3.13 — тесты сломались
FROM python:latest

# ✅ Фиксированная версия
FROM python:3.12-slim

Всегда указывайте конкретную версию. latest — это не версия, а указатель на «последнюю», и она меняется без предупреждения.

7. Docker Compose — поднимаем окружение целиком

Коротко: Docker Compose нужен, когда одного контейнера мало и нужно поднять сразу несколько сервисов: базу, API, Redis, фронтенд и тесты.

docker run — это один контейнер. Но для тестирования обычно нужно несколько: база, API, кэш, очередь. Запускать каждый руками — мучение. Docker Compose позволяет описать всё в одном файле и поднять одной командой.

7.1 Пример: API + PostgreSQL + Redis

Что здесь происходит:

Сервис

Роль

Главное, что нужно заметить

db

PostgreSQL

Есть environment, проброс порта и healthcheck

redis

Кэш / очередь

Минимальный сервис, который просто поднимается на своём образе

api

Приложение

Ходит в базу по адресу db:5432 и зависит от готовности других сервисов

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

Как читать этот файл сверху вниз:

  1. Поднимаем базу db и задаём ей переменные окружения

  2. Открываем порт 5432, чтобы к ней можно было подключиться с хоста

  3. Проверяем через healthcheck, что база действительно готова

  4. Поднимаем redis как отдельный сервис

  5. Поднимаем api, передаём ему адреса зависимостей и ждём готовности db

7.2 Разбор ключевых параметров

Параметр

Что делает

Пример

image

Готовый образ из Docker Hub

image: postgres:16

build

Собрать образ из Dockerfile

build: ./backend

ports

Проброс портов наружу

"5432:5432"

environment

Переменные окружения

POSTGRES_DB: testdb

volumes

Монтирование файлов/данных

./init.sql:/docker-entrypoint-initdb.d/init.sql

depends_on

Порядок запуска сервисов

depends_on: [db, redis]

healthcheck

Проверка готовности сервиса

test: ["CMD", "pg_isready"]

Обратите внимание: имена сервисов (dbredisapi) работают как DNS-имена внутри Docker-сети. API обращается к базе по адресу db:5432, а не localhost:5432.

7.3 Команды Docker Compose

Команда

Что делает

Когда использовать

docker compose up -d

Поднять всё в фоне

Начало работы

docker compose ps

Статус всех сервисов

Проверить, что всё запустилось

docker compose logs api

Логи конкретного сервиса

Сервис не отвечает

docker compose logs -f

Следить за всеми логами

Воспроизведение бага

docker compose down

Остановить и удалить контейнеры

Конец работы

docker compose down -v

То же + удалить volumes (данные)

Нужен чистый старт

docker compose restart api

Перезапустить один сервис

Изменили конфиг

docker compose up --build

Пересобрать образы и запустить

Изменили Dockerfile или код

⚠️ Ловушка: down без -v

docker compose down не удаляет volumes. Если тесты записали мусор в БД — при следующем up данные останутся. Используйте down -v для чистого старта.

8. Volumes — данные, которые переживут контейнер

Коротко: Контейнеры одноразовые, а volumes позволяют сохранить данные между перезапусками или подмонтировать внутрь контейнера файлы с вашей машины.

Контейнер — штука одноразовая. Удалили контейнер — данные внутри пропали. Volumes решают эту проблему.

8.1 Два типа привязок

services:
  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db-data:

Тип

Синтаксис

Когда использовать

Кто управляет

Named volume

db-data:/var/lib/...

Данные БД, кэш

Docker

Bind mount

./init.sql:/docker-entrypoint-initdb.d/init.sql

Конфиги, init-скрипты, исходный код

Вы (файл на хосте)

8.2 Практический пример

./init.sql:/docker-entrypoint-initdb.d/init.sql — файл с хоста монтируется внутрь контейнера.

Что произойдёт при запуске:

  1. Docker поднимет контейнер с PostgreSQL

  2. Файл init.sql окажется внутри контейнера в специальной папке

  3. PostgreSQL автоматически выполнит этот SQL при первом запуске

Зачем это QA: так удобно быстро заполнить базу тестовыми данными без ручных INSERT-запросов после каждого старта.

8.3 Команды для работы с volumes

Команда

Что делает

docker volume ls

Список всех volumes

docker volume inspect db-data

Подробная информация о volume

docker volume rm db-data

Удалить конкретный volume

docker volume prune

Удалить все неиспользуемые volumes

9. Docker для тестов: практические сценарии

Коротко: На практике Docker у QA чаще всего используется для трёх вещей: тестовая БД, браузер для UI-тестов и полный стенд из нескольких сервисов.

9.1 Тестовая БД с начальными данными

Идея: вы поднимаете чистую PostgreSQL, а Docker сам заливает стартовые данные из init.sql.

Создайте файл 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 — и всё чисто.

Шаг

Что делаете

Что получаете

1

Пишете init.sql

Набор стартовых таблиц и тестовых данных

2

Монтируете файл в контейнер

PostgreSQL видит init-скрипт при старте

3

Запускаете docker compose up -d

Готовую БД, к которой можно сразу подключаться

9.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 не хватает — тесты падают с непонятными ошибками

Порт 4444

WebDriver API — сюда подключаются тесты

Порт 7900

VNC — можно подключиться и наблюдать, что делает браузер (пароль: secret)

9.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

Сервис

Зачем нужен

db

Хранит данные приложения

api

Бэкенд, который работает с базой

frontend

UI, через который тестируется пользовательский сценарий

tests

Контейнер с автотестами, который ходит во фронтенд и API

Запуск:

docker compose up --abort-on-container-exit tests

Одна команда — и весь стенд поднимается, тесты прогоняются, всё останавливается. Флаг --abort-on-container-exit завершает все сервисы, когда контейнер tests завершится.

10. Networking — почему контейнеры (не) видят друг друга

Коротко: Самая частая ошибка новичков — использовать localhost между контейнерами. В Docker сервисы общаются по именам, а не через localhost.

Это самый частый источник проблем. Разберёмся раз и навсегда.

10.1 Три правила Docker-сети

#

Правило

Что это значит

1

Контейнеры видят друг друга по имени сервиса

API обращается к базе по db:5432, не localhost:5432

2

localhost внутри контейнера = сам контейнер

Если API обращается к localhost:5432 — он ищет Postgres внутри себя

3

Порты через -p нужны только для хоста

Контейнеры между собой общаются по внутренней сети без проброса

10.2 Схема

┌─────────────────────────────────────────┐
│              Docker Network             │
│                                         │
│  ┌─────────┐         ┌─────────┐       │
│  │   api   │───5432──│   db    │       │
│  │  :8080  │         │  :5432  │       │
│  └────┬────┘         └─────────┘       │
│       │                                 │
└───────┼─────────────────────────────────┘
        │ -p 8080:8080
        │
   ┌────┴────┐
   │  Host   │  ← ваш ноутбук
   │  :8080  │
   └─────────┘

10.3 Как достучаться до хоста из контейнера

Запомнить: если один контейнер хочет сходить к другому контейнеру — используйте имя сервиса. Если контейнер хочет сходить на вашу машину — используйте адрес хоста из таблицы ниже.

Платформа

Адрес хоста

Docker Desktop (Windows/Mac)

host.docker.internal

Linux

--network host или 172.17.0.1

Задача с собеседования: connection refused к БД

«Тесты подключаются к БД по localhost:5432, но получают connection refused. Контейнер с БД запущен. В чём проблема?»

Ответ: если тесты тоже в контейнере — нужно использовать имя сервиса (db:5432), а не localhost. Если тесты на хосте — нужно убедиться, что порт проброшен (-p 5432:5432).

11. Переменные окружения и .env

Коротко: Пароли, порты и URL-ы лучше выносить в .env, чтобы не хардкодить их в docker-compose.yml и проще переопределять в CI.

Не хардкодьте пароли и URL-ы в docker-compose.yml. Используйте .env.

11.1 Файл .env

# .env
POSTGRES_USER=tester
POSTGRES_PASSWORD=secret
POSTGRES_DB=testdb
API_PORT=8080

11.2 Использование в docker-compose.yml

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    ports:
      - "${DB_PORT:-5432}:5432"

Как читать этот пример:

  1. Docker Compose берёт значения из файла .env

  2. Подставляет их в секцию environment

  3. Если DB_PORT не задан, использует значение по умолчанию 5432

11.3 Синтаксис подстановки

Синтаксис

Что делает

Пример

${VAR}

Подставить значение переменной

${POSTGRES_USER} → tester

${VAR:-default}

Если не задана — использовать default

${DB_PORT:-5432} → 5432

${VAR:?error}

Если не задана — ошибка

${API_KEY:?API_KEY required}

Docker Compose автоматически подхватывает файл .env из текущей директории. Не нужно ничего дополнительно указывать.

12. Docker в CI/CD

Коротко: В CI Docker помогает повторить локальное окружение, но сервисы всё равно нужно дождаться, протестировать и в конце корректно убрать.

На CI Docker используется постоянно. Типичный пайплайн:

12.1 Пример для GitHub Actions

# .github/workflows/tests.yml
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

12.2 Ключевые моменты

Как этот пайплайн работает по шагам:

  1. Забираем код из репозитория

  2. Поднимаем сервисы через Docker Compose

  3. Ждём, пока база реально начнёт принимать соединения

  4. Запускаем тесты в отдельном контейнере

  5. В конце обязательно удаляем контейнеры и volumes

Что

Зачем

if: always() в Cleanup

Контейнеры будут остановлены даже если тесты упали

Wait for DB

depends_on не гарантирует, что Postgres принимает соединения

docker compose run tests

Запускает одноразовый контейнер — отработал и остановился

down -v

Чистим volumes, чтобы следующий запуск был с нуля

13. Задачи с реальных собеседований

Коротко: На собеседованиях обычно не просят глубокой DevOps-экспертизы. Чаще проверяют, понимаете ли вы причины типовых проблем и можете ли быстро их локализовать.

Задача 1: «У меня локально работает, а в CI нет»

Тесты используют API на localhost:8080. Локально всё работает. В CI (GitHub Actions) — connection refused. Docker Compose одинаковый. В чём может быть проблема?

Где запускаются тесты

Какой адрес использовать

На хосте

localhost:8080

В контейнере

api:8080

Ответ: локально тесты запускаются на хосте и обращаются к порту, проброшенному через -p. В CI тесты, скорее всего, запускаются в контейнере — значит, localhost:8080 больше не работает. Нужно использовать имя сервиса api:8080 или запускать тесты через docker compose run, где сеть уже общая.

Задача 2: «Тесты влияют друг на друга»

Тесты по отдельности зелёные, а вместе — падают. Один тест создаёт пользователя с email test@test.com, другой тоже. При последовательном запуске второй ловит unique constraint violation. Как исправить с помощью Docker?

Проблема

Причина

Решение

Тесты падают только вместе

Один тест оставляет данные, которые ломают следующий

Поднимать чистую БД перед прогоном или делать откат транзакций

Ответ: самый прямой способ через Docker — поднимать чистую БД перед каждым набором тестов: docker compose down -v && docker compose up -d. Более аккуратный вариант — транзакции с ROLLBACK в конце теста. Но Docker-подход проще объяснить и проще воспроизвести на собеседовании.

Задача 3: «Контейнер стартует, но сервис не отвечает»

docker compose up -d прошёл успешно, docker compose ps показывает все контейнеры running. Но API возвращае�� 502. Как дебажить?

Ответ — пошаговый чек-лист:

Шаг

Команда

Что проверяем

1

docker compose logs api

Ошибки в логах API

2

docker compose logs db

БД запустилась без ошибок?

3

docker compose exec api curl localhost:8080/health

API отвечает изнутри контейнера?

4

docker compose ps

Статус healthy vs running

5

docker inspect <container>

Сеть и порты корректны?

Задача 4: «Объясни, что делает этот docker-compose.yml»

Интервьюер показывает файл и просит объяснить каждую строку. Для этого нужно понимать:

Что спрашивают

Что нужно знать

services

Блок с описанием контейнеров

image vs build

Готовый образ из Hub vs собрать из Dockerfile

depends_on

Порядок запуска (не готовности!)

healthcheck

Проверка реальной готовности сервиса

volumes

Данные, которые переживут контейнер

docker compose down -v

-v удаляет volumes, без — оставляет

Задача 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

14. 5 ловушек, на которых валятся кандидаты

Коротко: Почти все ошибки в Docker у QA сводятся к пяти вещам: не тот адрес, не та готовность сервиса, временные данные, забытые volumes и путаница в командах запуска.

#

Ловушка

В чём проблема

Правильно

1

localhost ≠ хост

localhost внутри контейнера — это сам контейнер, не ваша машина

Между контейнерами — имя сервиса. До хоста — host.docker.internal

2

depends_on ≠ готовность

depends_on ждёт запуска контейнера, не готовности сервиса

condition: service_healthy + healthcheck

3

Данные временные

Удалили контейнер — данные пропали

Volumes для сохранения. Для тестов часто это плюс

4

down ≠ down -v

Без -v volumes сохраняются, данные «протекают» между запусками

docker compose down -v для чистого старта

5

CMD ≠ ENTRYPOINT

CMD можно переопределить при docker runENTRYPOINT — нет

Для тестов — CMD, чтобы запускать конкретные тесты

15. Чек-лист: что знать перед собеседованием

Коротко: Если понимаете таблицу ниже и можете на практике поднять БД, посмотреть логи и объяснить docker-compose.yml, для большинства QA-собеседований этого уже достаточно.

Тема

Минимум

Концепции

Image vs container, Dockerfile, Docker Compose, контейнер vs VM

Команды

runpsstoprmlogsexecbuildinspect

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 решает «у меня работает», ожидание готовности

Отладка

logsexecinspect — три главных инструмента

Этого хватит для 90% QA-собеседований. Не нужно знать Kubernetes, Docker Swarm или multi-stage builds — это уже DevOps-территория.

16. Полезные ресурсы

Коротко: Ниже только те ресурсы, которые реально помогают быстро войти в тему: официальный старт, песочница, документация по Compose и хорошая подборка ссылок.

#

Ресурс

Описание

1

Docker Getting Started

Официальный туториал. Хорошо структурирован, с примерами

2

Play with Docker

Браузерная песочница. Docker прямо в браузере, ничего не устанавливая

3

Docker Compose docs

Справочник по docker-compose.yml