Containers и service containers
Job containers, Docker service containers для PostgreSQL/Redis/queues, health checks, ports, volumes, credentials и отличия от Docker container actions.
Containers и service containers
В GitHub Actions слово «container» встречается в нескольких разных местах, и из-за этого легко перепутать три механизма. jobs.<job_id>.container задаёт container image, внутри которого выполняются run steps job. jobs.<job_id>.services поднимает соседние service containers: PostgreSQL, Redis, MySQL, MinIO, Elasticsearch, очереди, mock API. Docker container action — это уже переиспользуемый action с action.yml, который запускается как отдельный action, а не как база данных рядом с тестами.
Если коротко: job container — это «где выполняется мой test runner», service container — «какие временные зависимости нужны тестам», Docker container action — «упакованная команда, которую я вызываю через uses». Эта граница важна и для CI pipeline, и для Self-hosted runners, потому что контейнеры упрощают runtime, но не превращают workflow в полноценный Kubernetes или Docker Compose.
flowchart TD
R[Ubuntu runner / Docker host] --> JC[Job container: node:20]
R --> PG[Service container: postgres:16]
R --> RD[Service container: redis:7]
JC <-->|hostname postgres:5432| PG
JC <-->|hostname redis:6379| RD
JC --> ST[run steps: install, migrate, test]
subgraph Same Docker network
JC
PG
RD
endJob container: зафиксировать runtime job
Job container полезен, когда тестам нужна конкретная среда: например, node:20-bookworm-slim, python:3.12, ruby:3.3 или внутренний image с уже установленными browser dependencies. Runner всё равно нужен: на GitHub-hosted это обычно Ubuntu runner, который становится Docker host. Но run steps выполняются уже внутри указанного container image.
name: integration-ci
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-24.04
container:
image: node:20-bookworm-slim
env:
NODE_ENV: test
options: --cpus 2
steps:
- uses: actions/checkout@v4
- run: node --version
- run: npm ci
- run: npm testЕсть два практичных нюанса. Во-первых, default shell для run steps внутри container — sh, а не bash; если скрипты используют bash-синтаксис, задавайте shell: bash или defaults.run.shell. Во-вторых, если step вызывает container action, такой action запускается как sibling container на той же Docker-сети и с теми же volume mounts, а не «внутри» вашего job container.
Service containers: временная инфраструктура для одного job
Service container живёт только в рамках одного job. GitHub создаёт отдельный Docker container для каждого сервиса из services, даёт steps доступ к этим сервисам и удаляет их после завершения job. Это хорошо подходит для integration tests: база не production, Redis пустой, состояние воспроизводимое, секреты минимальны.
Пример: Node-тесты внутри job container, рядом PostgreSQL и Redis.
jobs:
integration:
runs-on: ubuntu-24.04
container: node:20-bookworm-slim
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:integration
env:
DATABASE_URL: postgres://app:app@postgres:5432/app_test
REDIS_URL: redis://redis:6379Обратите внимание на hostnames: postgres и redis — это labels из services. Когда job сам работает в container, GitHub соединяет job container и service containers через Docker bridge network. Внутри этой сети сервис доступен по label, а порты между контейнерами открыты без явного ports. Поэтому postgres:5432, а не localhost:5432.
Когда нужен localhost и ports
Если job выполняется прямо на runner machine, без container:, картина меняется. Ваши run steps работают на host, а PostgreSQL или Redis сидят в Docker container. Тогда порт нужно опубликовать на Docker host, и приложение подключается к localhost.
jobs:
integration:
runs-on: ubuntu-24.04
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: app
POSTGRES_DB: app_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run test:integration
env:
DATABASE_URL: postgres://postgres:app@localhost:5432/app_testЭто частый источник падений: workflow переносит тесты из runner job в job container, но оставляет localhost; или наоборот, убирает container:, но продолжает ходить на postgres. Проверка простая: если job container есть — hostname равен service label. Если job container нет — публикуйте порт и используйте localhost:<port>.
Если host port не указан, GitHub может выбрать свободный порт сам, а номер доступен через context вроде job.services.redis.ports[6379]. Это удобно, когда нужно избежать конфликтов, но в обычном CI чаще проще явно закрепить порт.
Health checks: ждать готовность сервиса, а не только старт контейнера
Docker container может быть «запущен», но база ещё применяет init scripts, Redis ещё не отвечает, Elasticsearch ещё прогревается. Поэтому для сервисов почти всегда стоит задавать options с Docker health check: --health-cmd, --health-interval, --health-timeout, --health-retries.
Для PostgreSQL типичный минимум:
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5Для Redis:
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5Health check не заменяет миграции и seed. Он только отвечает на вопрос «сервис принимает соединения?». Если тестам нужна схема БД, добавьте отдельный step: npm run db:migrate, rails db:test:prepare, alembic upgrade head, prisma migrate deploy — что принято в вашем стеке.
Volumes, credentials и private images
У container и services можно задавать env, ports, volumes, options, а для private registry — credentials. Для job container GitHub поддерживает named volumes, anonymous volumes и bind mounts с абсолютным host path. Но в CI лучше относиться к volumes осторожно: чем больше локального state, тем труднее понять, почему job проходит на одном runner и падает на другом. Это особенно заметно на Self-hosted runners, где host не обязательно чистый между jobs.
Private image выглядит так:
container:
image: ghcr.io/acme/ci-node:20
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GHCR_READ_TOKEN }}Для service container синтаксис похож:
services:
testdb:
image: ghcr.io/acme/postgres-with-extensions:16
credentials:
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_READ_TOKEN }}Секреты здесь остаются обычными secrets из Variables, env и secrets. Не кладите production credentials в test services. Если image нужен только для CI, лучше дать токен с read-only доступом к registry, чем универсальный PAT.
Отличие от Docker container actions
Docker container action создаётся как action с metadata-файлом action.yml или action.yaml, где описаны inputs, outputs и runs. Его вызывают через uses, как любую другую action из Marketplace actions и pinning. Он хорош для переиспользуемой логики: генерация отчёта, custom scanner, упаковка артефакта, специфичный CLI с зависимостями.
Service container не вызывается через uses и не имеет action.yml. Он не «выполняет шаг», а предоставляет сетевой сервис для steps. Поэтому PostgreSQL как services.postgres — нормально. PostgreSQL как Docker container action — почти всегда неверная модель.
Есть ещё ограничение по платформе: job containers, service containers и Docker container actions требуют Linux runner. На GitHub-hosted runners это означает Ubuntu. На self-hosted — Linux machine с установленным Docker. Для Windows/macOS jobs используйте обычные setup-actions, внешние тестовые сервисы или отдельные Linux jobs, которые передают результат через Artifacts и reports.
Практичная эвристика
Используйте job container, когда важен одинаковый runtime для команд: версия языка, системные библиотеки, CLI, headless browser dependencies. Используйте service containers, когда тестам нужна временная инфраструктура: база, cache, queue, fake S3, SMTP catcher, browser grid. Используйте Docker container action, когда хотите переиспользовать команду как action в разных repositories.
Если схема становится похожа на большой docker-compose.yml с пятью сервисами, кастомными сетями, bind mounts и init-order, остановитесь и пересмотрите дизайн. Иногда лучше собрать отдельный test image. Иногда — поднять ephemeral environment перед job. Иногда — разделить Matrix strategy, чтобы тяжёлые integration tests не тормозили быстрый feedback loop. Containers в Actions должны делать CI pipeline воспроизводимее, а не превращать его в скрытую staging-инфраструктуру.
See also
- CI pipeline — где service containers обычно появляются в build/test цепочке.
- GitHub-hosted runners — Ubuntu runners как Docker host для container jobs.
- Self-hosted runners — почему Docker на своём runner требует отдельной эксплуатационной дисциплины.
- Matrix strategy — как тестировать несколько версий PostgreSQL, Redis или runtime.
- Variables, env и secrets — как передавать connection strings и registry credentials.
- Artifacts и reports — как передавать результаты между jobs вместо шаринга локального state.
- Docker container actions — когда container становится переиспользуемым action, а не сервисом для тестов.
- Secure use и threat model — почему контейнеры не отменяют модель угроз CI.
Внешние справки: GitHub Docs по job containers, service containers, примерам PostgreSQL, Redis и metadata syntax для actions.
