Learn (Almost) Anything
КаталогСкачать курс

Автоматизация в GitHub

Контейнерные actions для контролируемого Linux-окружения: Dockerfile, entrypoint, inputs через args/env, latency tradeoffs и требования к runners.

ruЭнциклопедия33 урока6 модулей9
GitHub actionsАвтоматизация в GitHubМодель workflowсостояние и артефактыматрицы и runtimeрелизы и environmentsПереиспользование и собственные actionsgovernance и эксплуатацияgithubactions

Docker container actions

Контейнерные actions для контролируемого Linux-окружения: Dockerfile, entrypoint, inputs через args/env, latency tradeoffs и требования к runners.

Docker container actions

Docker container action — это custom action, в которой код запускается внутри Docker-контейнера. В action.yml вы описываете публичный контракт action, а реальное окружение собираете в Dockerfile или берёте из опубликованного image через docker://.... Главная причина использовать этот тип action — контролируемое Linux-окружение: конкретная версия CLI, системные пакеты, Python/Ruby/Go-бинарники, нативные зависимости, утилиты вроде imagemagick, terraform, kubectl или собственного сканера.

Это не «ещё один способ написать shell». Если логика состоит из нескольких переносимых команд, чаще достаточно Composite actions. Если нужна кроссплатформенность на Ubuntu, Windows и macOS без тяжёлого окружения, лучше смотреть на JavaScript actions. Docker container action оправдана, когда стабильность runtime важнее скорости старта.

flowchart TD A[Caller workflow step: uses owner/action@v1] --> B[action.yml] B --> C{runs.image} C -->|Dockerfile| D[Runner builds local image] C -->|docker:// image| E[Runner pulls image] D --> F[docker run] E --> F B --> G[runs.args from inputs] G --> F F --> H[ENTRYPOINT /entrypoint.sh] H --> I[Reads workspace and env files] I --> J[Writes logs, annotations, GITHUB_OUTPUT]
flowchart TD
  A[Caller workflow step: uses owner/action@v1] --> B[action.yml]
  B --> C{runs.image}
  C -->|Dockerfile| D[Runner builds local image]
  C -->|docker:// image| E[Runner pulls image]
  D --> F[docker run]
  E --> F
  B --> G[runs.args from inputs]
  G --> F
  F --> H[ENTRYPOINT /entrypoint.sh]
  H --> I[Reads workspace and env files]
  I --> J[Writes logs, annotations, GITHUB_OUTPUT]
Как GitHub Actions превращает Docker action из metadata в запуск контейнера.

Минимальная структура

Типичный repo для Docker action выглядит так:

my-docker-action/
  action.yml
  Dockerfile
  entrypoint.sh
  README.md

action.yml задаёт metadata: имя, inputs, outputs и способ запуска. Для Docker action runs.using всегда равен docker, а runs.image указывает либо на локальный Dockerfile, либо на публичный image:

name: "Validate release manifest"
description: "Validate release.json with pinned CLI dependencies"

inputs:
  manifest-path:
    description: "Path to release manifest"
    required: false
    default: "release.json"
  strict:
    description: "Fail on warnings"
    required: false
    default: "false"

outputs:
  valid:
    description: "true when the manifest is valid"

runs:
  using: "docker"
  image: "Dockerfile"
  args:
    - ${{ inputs.manifest-path }}
    - ${{ inputs.strict }}

В caller workflow такая action выглядит как обычный uses step:

jobs:
  release-check:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6
      - id: manifest
        uses: acme/validate-release-manifest@v1
        with:
          manifest-path: dist/release.json
          strict: "true"
      - run: echo "valid=${{ steps.manifest.outputs.valid }}"

Обратите внимание на уровень запуска: это всё ещё step внутри job. Если нужно переиспользовать целый pipeline с runs-on, permissions, environment, matrix и несколькими jobs, это зона Reusable workflows, а не Docker action.

Dockerfile и entrypoint

Минимальный Dockerfile должен начинаться с FROM. Для GitHub Actions лучше использовать конкретный tag, а не latest, чтобы поведение action не менялось случайно при новом pull image.

FROM alpine:3.20

RUN apk add --no-cache jq bash

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh получает значения из runs.args как позиционные аргументы. Это самый прозрачный способ передавать inputs в Docker action:

#!/usr/bin/env sh
set -eu

manifest_path="${1:-release.json}"
strict="${2:-false}"

if [ ! -f "$manifest_path" ]; then
  echo "::error::Manifest not found: $manifest_path"
  echo "valid=false" >> "$GITHUB_OUTPUT"
  exit 1
fi

warnings=$(jq '.warnings | length // 0' "$manifest_path")

if [ "$strict" = "true" ] && [ "$warnings" -gt 0 ]; then
  echo "::error::Manifest contains $warnings warning(s)"
  echo "valid=false" >> "$GITHUB_OUTPUT"
  exit 1
fi

echo "valid=true" >> "$GITHUB_OUTPUT"
echo "Manifest is valid: $manifest_path"

Здесь используются те же workflow commands, что и в Workflow commands и summaries: ::error::... создаёт аннотацию в UI, а запись в $GITHUB_OUTPUT выставляет output step. Для больших отчётов не надо пихать JSON в output; лучше сохранить файл и загрузить его через Artifacts и reports.

Inputs: args, env и кавычки

GitHub metadata позволяет объявлять inputs, но для Docker action важно явно передать их в runs.args. Внутри контейнера эти значения приходят в ENTRYPOINT как аргументы. Так проще избежать сюрпризов с именами переменных, дефисами в input id и shell-escaping.

Можно читать и environment variables, например GITHUB_SHA, GITHUB_WORKSPACE, GITHUB_OUTPUT, но не смешивайте это с секретами без необходимости. Если action нужен token, передавайте его через with только когда он действительно нужен, документируйте минимальные Permissions и GITHUB_TOKEN, не печатайте token и не дампите весь environment в лог.

Есть тонкость Docker ENTRYPOINT: exec form вроде ENTRYPOINT ["echo", "$GITHUB_SHA"] не делает shell-подстановку переменных. Если нужна подстановка, запускайте shell явно или, чаще, используйте entrypoint script. Поэтому файл entrypoint.sh — не лишняя обвязка, а нормальный слой адаптации между GitHub Actions и контейнером.

# Не подставит переменную: Docker передаст строку "$GITHUB_SHA" как аргумент.
ENTRYPOINT ["echo", "$GITHUB_SHA"]

# Подставит переменную, потому что shell выполняет expansion.
ENTRYPOINT ["sh", "-c", "echo \"$GITHUB_SHA\""]

GITHUB_WORKSPACE и файловая система

Перед запуском контейнера GitHub монтирует workspace репозитория и выставляет путь в GITHUB_WORKSPACE. Это позволяет action читать checked-out код, генерировать файлы, валидировать manifests, запускать CLI над проектом.

Но у Docker actions есть несколько практических правил. Не задавайте USER в Dockerfile: официальная документация GitHub предупреждает, что Docker actions должны выполняться default Docker user, иначе можно потерять доступ к GITHUB_WORKSPACE. Не полагайтесь на WORKDIR для entrypoint; используйте абсолютный путь к script. И всегда проверяйте, что entrypoint executable, иначе runner упадёт с ошибкой уровня OCI runtime, которая выглядит гораздо страшнее реальной причины.

Runtime-ограничения

Docker container actions запускаются только на Linux runners. На GitHub-hosted runners это значит ubuntu-*; на self-hosted runner нужна Linux-машина с установленным Docker. Если workflow должен работать на Windows и macOS matrix, Docker action либо не подходит, либо её нужно вызывать только в Linux job. Это важно при проектировании Matrix strategy, GitHub-hosted runners и Self-hosted runners.

strategy:
  matrix:
    os: [ubuntu-24.04, windows-latest, macos-latest]

runs-on: ${{ matrix.os }}

steps:
  - uses: actions/checkout@v6
  - name: Validate manifest in Docker action
    if: runner.os == 'Linux'
    uses: acme/validate-release-manifest@v1

Ещё одна цена — latency. Если image: Dockerfile, runner должен собрать image перед запуском. Если image: docker://registry/name:tag, он должен скачать image. На маленькой action это может быть заметно медленнее, чем JavaScript actions. Поэтому Dockerfile стоит держать компактным: маленький base image, минимум слоёв с тяжёлыми пакетами, понятный .dockerignore, pinned versions. Dependency cache помогает workflow в целом, но не превращает большой контейнер в бесплатный startup.

Когда использовать docker://...

Вместо сборки из локального Dockerfile можно сослаться на уже опубликованный image:

runs:
  using: "docker"
  image: "docker://ghcr.io/acme/release-validator:1.4.2"
  args:
    - ${{ inputs.manifest-path }}

Это удобно, когда image собирается отдельно, подписывается, сканируется и переиспользуется не только в Actions. Но trust model становится ближе к Marketplace actions и pinning: tag может быть перезаписан, registry может быть внешним, supply chain нужно контролировать. Для production-критичных workflow лучше использовать immutable digest или строгую release-политику, связанную с Versioning и maintenance actions.

runs:
  using: "docker"
  image: "docker://ghcr.io/acme/release-validator@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

Граница ответственности

Хорошая Docker action делает одну понятную операцию: запускает конкретный tool, нормализует входы, пишет outputs, даёт читаемые errors. Плохая Docker action прячет внутри себя всю CD-политику: approvals, cloud credentials, production environment, branch rules и deploy-решения. Эти вещи должны оставаться видимыми в workflow, Environments и protection rules и Deployment workflows.

С точки зрения безопасности Docker action не отменяет обычные правила Secure use и threat model. Treat inputs as untrusted: branch names, PR titles, file paths и JSON из репозитория могут прийти от атакующего. Кавычки в shell, allowlist путей, минимальные permissions и отсутствие лишних secret logs здесь важнее, чем сам факт запуска в контейнере.

See also

Внешние справки: GitHub Docs по creating a Docker container action, metadata syntax, custom actions и Dockerfile support for GitHub Actions.

Быстрое повторение #rc-1

Когда Docker container action оправдана сильнее, чем composite или JavaScript action?

Показать ответ

Когда важнее контролируемое Linux-окружение: pinned CLI, системные пакеты, нативные зависимости или собственный toolchain. Цена за это — более тяжёлый startup и привязка к Linux runners.

Источники

  1. Creating a Docker container action - GitHub Docs
  2. Metadata syntax reference - GitHub Docs
  3. About custom actions - GitHub Docs
  4. Dockerfile support for GitHub Actions - GitHub Docs