Docker-экшены: полный контроль окружения ценой только-Linux и накладных образа
Docker-экшен упаковывает всё своё окружение в контейнер — любой язык, любую системную зависимость — но работает только на Linux-раннерах и платит 30-90с pull или сборку образа за прогон против composite или JS-экшена в миллисекундах.
Команде нужен был релизный экшен, запускавший Go-бинарь, cosign (инструмент подписи артефактов из проекта Sigstore) и запиненную версию skopeo — тулчейн, которого не было npm-пакетами и который означал бы скриптинг хрупких загрузок в composite-экшене. Так что они потянулись к Docker-экшену: Dockerfile, который FROM-ил известную базу, ставил точные инструменты и запускал entrypoint. Работало идеально. Затем они попробовали добавить экшен в воркфлоу, что бежал на macos-latest ради отдельного требования подписи, и джоба упала до запуска entrypoint: Docker-контейнерные экшены поддерживаются только на Linux-раннерах. А на Linux-джобах экшен, что в тестах казался быстрым, теперь добавлял плоские 40-70 секунд к каждому прогону — раннер собирал образ из Dockerfile на каждом вызове, ведь ничто его не пресобирало. Контейнер купил им произвольное окружение и взял с них стартовый налог произвольного окружения и привязку к ОС на каждом единственном прогоне.
К концу урока ты будешь точно знать, когда Docker — единственный жизнеспособный тип экшена, что такое две цены, платимые на каждом прогоне, и как вдвое срезать стартовый налог, не отказываясь от контейнера.
Полный контроль окружения
Когда composite- и JavaScript-экшены не справляются — потому что тебе нужен cosign, конкретный glibc или скомпилированный бинарь без npm-эквивалента — Docker-экшен не костыль: это единственный тип, созданный владеть всем своим окружением. Вопрос не в том, достаточно ли он мощный; вопрос в том, оправдана ли цена для твоего случая.
Docker-экшен объявляет runs.using: "docker" и либо image: "Dockerfile" (собирается на раннере), либо image: "docker://ghcr.io/org/img:tag" (тянется пресобранным). Внутри контейнера вы контролируете всё — базовую ОС, системные библиотеки, любой языковой рантайм, точные версии инструментов — что не могут дать composite- и JavaScript-экшены. Если вашему экшену нужен cosign, конкретный glibc, скомпилированный бинарь Go или Rust или инструмент без npm-эквивалента, контейнер — единственный тип, упаковывающий это воспроизводимо:
FROM alpine:3.20
RUN apk add --no-cache cosign skopeo
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]# action.yml
runs:
using: "docker"
image: "Dockerfile"
args:
- ${{ inputs.tag }}
env:
REGISTRY_TOKEN: ${{ inputs.token }}Контракт рантайма отличается от JavaScript-экшена. Inputs приходят как переменные окружения INPUT_* в контейнере (input tag становится INPUT_TAG), и их можно также передать позиционно через args:. Outputs пишутся дозаписью в файл, названный $GITHUB_OUTPUT — echo "result=ok" >> "$GITHUB_OUTPUT" изнутри entrypoint — а рабочий каталог bind-монтируется в $GITHUB_WORKSPACE. Никакого toolkit; вы говорите на протоколе напрямую через env-переменные и эти файлы.
Команда добавляет рабочий Docker-контейнерный экшен в джобу, что бежит на `runs-on: macos-latest`. Джоба падает до выполнения entrypoint экшена. В чём причина?
Цена: только-Linux и накладные образа
У силы контейнера две цены, обе платятся на каждом прогоне. Первая: Docker-экшены работают только на Linux-раннерах — не macOS, не Windows. Экшен, что вы хотите пригодным везде, не может быть Docker-экшеном; одно это толкает многие переиспользуемые экшены к JavaScript. Вторая: контейнер должен материализоваться до запуска вашего кода. С image: "Dockerfile" раннер собирает образ на каждом вызове, если слои не закэшированы — обычно 40-90 секунд. С image: "docker://..." он тянет пресобранный образ — быстрее, но всё равно десятки секунд для холодного pull нетривиального образа. Сравните с composite (миллисекунды) или JavaScript-экшеном (субсекундный старт node): Docker-экшен добавляет плоский попрогонный налог, невидимый в долгой джобе и жестокий в быстрой, триггерящейся сотни раз в день.
Прежде чем добавить Docker-экшен в быстрый воркфлоу, срабатывающий сотни раз в день, спроси себя: требует ли логика произвольного окружения контейнера — или JavaScript-экшен справится? Если ответ «да, нужен Docker» — следующий вопрос: как срезать стартовый налог. Митигация — опубликовать пресобранный образ в реестр (GHCR) и сослаться на него через docker://, превращая попрогонную сборку в попрогонный pull и давая кэшированию слоёв помочь. Но это добавляет релизный пайплайн для самого образа. Две заметки о безопасности наследуются из мира контейнеров и значат здесь больше, чем для других типов: пиньте базовый образ по digest (FROM alpine:3.20@sha256:...), чтобы сборка была воспроизводимой и пере-тегированная апстрим-база не могла молча изменить ваш экшен; и пиньте любые экшены, что ваш воркфлоу использует для вызова этого, по commit SHA — та же supply-chain-гигиена, что у других типов. Правило решения резкое: выбирайте Docker, только когда вам по-настоящему нужно окружение, что composite и JavaScript дать не могут, и примите только-Linux плюс стартовый налог как цену этого контроля.
Docker-экшен с `image: "Dockerfile"` добавляет ~60 секунд к каждому прогону воркфлоу, что срабатывает сотни раз в день. Логика контейнера быстрая. Какой самый эффективный фикс, сохраняющий его Docker-экшеном?
- 01Какая уникальная способность Docker-экшена и две повторяющиеся цены, что с ней приходят?
- 02Как работают inputs и outputs у Docker-экшена и как снизить его попрогонную стоимость старта?
Docker-экшен ставит runs.using: "docker" и указывает image: либо на Dockerfile (собирается на раннере), либо на docker://-ссылку на пресобранный образ, и внутри этого контейнера вы контролируете всё окружение: базовую ОС, системные библиотеки, любой языковой рантайм, точные версии инструментов и инструменты без npm-эквивалента. Это та единственная способность, что composite- и JavaScript-экшены структурно дать не могут, и единственная причина выбрать Docker. Цена взимается на каждом единственном прогоне и приходит в двух частях. Первая: Docker-контейнерные экшены работают только на Linux-раннерах — не macOS, не Windows — так что экшен, задуманный пригодным везде, не может быть Docker-экшеном, что одно толкает большинство универсальных переиспользуемых экшенов к JavaScript. Вторая: образ должен материализоваться до запуска вашего entrypoint: сборка из image: "Dockerfile" добавляет примерно 40-90 секунд за прогон, если не закэширована, и даже пресобранный docker://-образ стоит десятки секунд на холодном pull — плоский налог, что исчезает внутри долгой джобы и доминирует в быстрой, триггерящейся сотни раз в день, против миллисекунд composite и субсекундного старта JS-экшена. Протокол рантайма голый: inputs приходят как env-переменные INPUT_* (и опциональные позиционные args:), outputs дозаписываются в $GITHUB_OUTPUT, репо монтируется в $GITHUB_WORKSPACE, и toolkit нет. Митигируй стартовый налог публикацией пресобранного образа в реестр и ссылкой через docker://, превращая попрогонную сборку в попрогонный pull с кэшированием слоёв, и пинь базовый образ по digest, чтобы сборка была воспроизводимой и пере-тегированная апстрим-база не могла молча изменить ваш экшен. Правило решения: выбирай Docker, лишь когда тебе по-настоящему нужно окружение, что другие типы дать не могут, и прими только-Linux плюс накладные образа как сознательную цену этого контроля. Теперь, когда встретишь воркфлоу-джобу, прибавляющую 60 секунд на каждом прогоне ради экшена, «просто запускающего скрипт», ты знаешь: проверь, не собирается ли там Dockerfile на каждом вызове — и ты знаешь ровно два рычага, к которым тянуться.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.