Tutorial: configurar runners efêmeros Docker no GitHub Actions com cache remoto para acelerar builds de monólitos PHP — passo a passo
Deploy & DevOps · Por Kevin Marcondes · Publicado em · 11 min de leitura
Tutorial: configurar runners efêmeros Docker no GitHub Actions com cache remoto para acelerar builds de monólitos PHP. Aqui você vai ver a arquitetura, a imagem do runner, o Docker Compose, o workflow YAML com BuildKit/Buildx e como ajustar o Dockerfile para aproveitar cache entre execuções, reduzindo tempo de build sem abrir mão de isolamento.
Pré-requisitos e quando faz sentido
Runners efêmeros são ideais quando você quer isolamento por job, alto paralelismo e zero acúmulo de estado entre execuções. O custo é perder caches locais; por isso, usaremos cache remoto.
Este tutorial — configurar runners efêmeros Docker no GitHub Actions com cache remoto para acelerar builds de monólitos PHP — foca em monólitos PHP (ex.: apps Laravel) que constroem imagens Docker.
Você precisa de um host Linux com Docker e acesso para criar containers do runner. O repositório no GitHub deve permitir runners self-hosted.
- Host Linux com Docker e Docker Compose (ou Compose Plugin).
- Acesso ao repositório ou organização no GitHub para criar token de runner.
- Git e SSH opcionais para pull de submódulos.
- Variáveis seguras para PAT (se for buscar token via API).
Arquitetura: runner efêmero + Docker + cache remoto
Cada job sobe em um container com o GitHub Actions Runner, registra-se com token efêmero, executa um único job e encerra. O build Docker é feito via BuildKit/Buildx usando cache remoto persistente entre execuções.
Como o runner é destruído após o job, o cache local do Docker não ajuda. Por isso, adotamos backends remotos de cache do BuildKit, adequados a ambientes efêmeros. Veja opções comuns:
| Backend de cache | Persiste entre runners? | Complexidade | Quando usar |
|---|---|---|---|
| local (padrão) | Não | Baixa | Ambientes fixos ou runners persistentes. |
| registry (type=registry) | Sim | Média | Você controla um registry; bom para times maiores. |
| GHA (type=gha) | Sim | Baixa | Integra-se ao GitHub Actions; ótimo para começar. |
Criar a imagem do runner efêmero em Docker
Usaremos a imagem oficial do runner como base e um entrypoint que registra em modo efêmero, executa o job e remove o runner. Montaremos o socket do Docker no container para permitir builds dentro do job. Montar o socket concede privilégios elevados ao container; use host dedicado e políticas de acesso.
# Crie Dockerfile e entrypoint do runner
mkdir -p runner-image && cd runner-image
cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM ghcr.io/actions/runner:latest
# Instale Docker CLI para usar o socket do host a partir do runner
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& chmod a+r /etc/apt/keyrings/docker.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends docker-ce-cli \
&& apt-get purge -y --auto-remove curl gnupg \
&& rm -rf /var/lib/apt/lists/*
# Entrypoint para registro efêmero
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER 1001
ENTRYPOINT ["/entrypoint.sh"]
EOF
cat > entrypoint.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
: "${RUNNER_URL:?Defina RUNNER_URL (repo ou org)}"
: "${RUNNER_TOKEN:?Defina RUNNER_TOKEN válido}"
: "${RUNNER_LABELS:=docker,linux,x64,ephemeral}"
: "${RUNNER_NAME:=runner-$(hostname)-$RANDOM}"
: "${RUNNER_WORKDIR:=/_work}"
cleanup() {
echo "Removendo runner..."
./config.sh remove --unattended --token "$RUNNER_TOKEN" || true
}
trap cleanup EXIT
./config.sh \
--url "$RUNNER_URL" \
--token "$RUNNER_TOKEN" \
--work "$RUNNER_WORKDIR" \
--labels "$RUNNER_LABELS" \
--name "$RUNNER_NAME" \
--ephemeral \
--unattended \
--replace
exec ./run.sh
EOF
# Construa a imagem do runner
docker build -t gha-ephemeral-runner:latest .
Orquestrar runners com Docker Compose e token dinâmico
O token de registro expira rapidamente, então gere um token novo antes de subir cada runner. Abaixo, um script simples usando PAT com escopo para ler o repositório e criar token de registro. Proteja o PAT em variável de ambiente segura. Você pode escalar múltiplos runners com Compose.
Para controle de recursos do host, veja como limitar CPU/memória em containers no guia de cgroup v2: cgroup v2 + systemd para Docker.
# Arquivos: get-token.sh, .env e docker-compose.yml
mkdir -p ../runner-compose && cd ../runner-compose
# Script para obter token de registro (repo)
cat > get-token.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Variáveis exigidas: GH_OWNER, GH_REPO, GH_PAT
: "${GH_OWNER:?}"
: "${GH_REPO:?}"
: "${GH_PAT:?}"
resp=$(curl -s -X POST \
-H "Authorization: token ${GH_PAT}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${GH_OWNER}/${GH_REPO}/actions/runners/registration-token)
echo "$resp" | jq -r .token
EOF
chmod +x get-token.sh
# Variáveis do ambiente do Compose
cat > .env <<'EOF'
GH_OWNER=seu-usuario-ou-org
GH_REPO=seu-repo
# Export GH_PAT no shell ao rodar o compose para não gravar no disco
RUNNER_URL=https://github.com/${GH_OWNER}/${GH_REPO}
RUNNER_LABELS=docker,linux,x64,ephemeral
EOF
# Docker Compose do runner efêmero
cat > docker-compose.yml <<'EOF'
services:
gha-runner:
image: gha-ephemeral-runner:latest
restart: "no"
environment:
RUNNER_URL: ${RUNNER_URL}
RUNNER_LABELS: ${RUNNER_LABELS}
RUNNER_TOKEN: ${RUNNER_TOKEN}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
resources:
limits:
cpus: '2.0'
memory: 4g
EOF
# Exemplo: iniciar 2 runners efêmeros
export GH_PAT=coloque_seu_token_aqui
export RUNNER_TOKEN=$(./get-token.sh)
RUNNER_TOKEN="$RUNNER_TOKEN" docker compose up -d --scale gha-runner=2
Workflow do GitHub Actions com BuildKit e cache remoto
Agora configure o workflow para usar o runner self-hosted por rótulos e habilitar cache remoto do BuildKit (type=gha) e cache do Composer. Se sua pipeline também constrói imagens multi-arquitetura, veja o tutorial de manifestos: Como criar imagens Docker multi‑arquitetura. Para fundamentos de cache remoto, confira: BuildKit e cache remoto no CI.
Com essa configuração, você aplica na prática o tutorial: configurar runners efêmeros Docker no GitHub Actions com cache remoto para acelerar builds de monólitos PHP, reduzindo tempo de build de forma previsível.
name: CI PHP (runner efêmero)
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: [self-hosted, linux, x64, ephemeral]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configurar Buildx
uses: docker/setup-buildx-action@v3
- name: Cache do Composer
uses: actions/cache@v4
with:
path: |
~/.cache/composer
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-
- name: Instalar dependências PHP (Composer)
run: |
echo "COMPOSER_CACHE_DIR=$HOME/.cache/composer" >> $GITHUB_ENV
composer install --no-interaction --prefer-dist
- name: Build da imagem com cache remoto (BuildKit)
env:
DOCKER_BUILDKIT: 1
run: |
docker buildx build \
--cache-from type=gha \
--cache-to type=gha,mode=max \
-t ghcr.io/${{ github.repository }}/app:${{ github.sha }} \
.
- name: Testes (exemplo)
run: |
vendor/bin/phpunit --colors=always --testdox
Ajuste do Dockerfile para monólitos PHP com camadas cacheáveis
Para acelerar ainda mais, estruture seu Dockerfile com multistage e mounts de cache do BuildKit. Separe dependências PHP (Composer) das camadas de código, minimize invalidações e use COPY seletivo. O exemplo abaixo usa Composer 2 como estágio de deps e PHP-FPM como runtime. Adapte extensões e ini conforme seu projeto.
# syntax=docker/dockerfile:1.7
########## deps ##########
FROM composer:2 AS deps
WORKDIR /app
# Apenas arquivos que influenciam as deps
COPY composer.json composer.lock ./
# Cache do Composer isolado
RUN --mount=type=cache,target=/tmp/composer-cache \
COMPOSER_CACHE_DIR=/tmp/composer-cache \
composer install --no-dev --no-interaction --prefer-dist --no-progress
########## builder ##########
FROM php:8.2-fpm-alpine AS builder
WORKDIR /app
# Extensões necessárias (exemplo)
RUN apk add --no-cache icu-dev libzip-dev oniguruma-dev git \
&& docker-php-ext-install intl zip mbstring
# Copie apenas o necessário para build
COPY --from=deps /app/vendor /app/vendor
COPY . .
# Opcional: build de assets se houver (npm/yarn) usando cache
# RUN --mount=type=cache,target=/root/.npm \
# npm ci && npm run build
########## runtime ##########
FROM php:8.2-fpm-alpine AS runtime
WORKDIR /var/www/html
# Extensões de runtime
RUN apk add --no-cache icu libzip oniguruma \
&& docker-php-ext-install opcache
# Ajustes de opcache (exemplo)
RUN { \
echo 'opcache.enable=1'; \
echo 'opcache.validate_timestamps=0'; \
echo 'opcache.jit=1255'; \
} > /usr/local/etc/php/conf.d/opcache.ini
COPY --from=builder /app /var/www/html
HEALTHCHECK --interval=30s --timeout=3s CMD php -v || exit 1
CMD ["php-fpm"]
Verificação e solução de problemas
No log do job, o nome do runner indica execução em self-hosted. Após o step de Buildx, procure por mensagens de cache hit/miss. Quando o cache remoto funciona, as camadas estáveis mostram recuperação rápida.
No step do Composer, o actions/cache indicará se restaurou a chave primária ou parcial. Em reexecuções com composer.lock inalterado, a instalação será visivelmente mais rápida.
Token inválido ou expirado: gere um novo imediatamente antes do docker compose up. Confirme escopos do PAT.
Build sem cache: garanta DOCKER_BUILDKIT=1, use docker/setup-buildx-action e mantenha as flags --cache-from/--cache-to consistentes.
Erro ao acessar /var/run/docker.sock: confira permissões do socket no host e se o volume está montado.
Instabilidade de rede: caches remotos dependem de boa conectividade; considere retentativas automáticas no job.
- Verifique labels do runner no cabeçalho do job.
- Procure por “Loaded cache”/“Reusing cache” no build.
- Confira o resumo do actions/cache para Composer.
- Valide variáveis RUNNER_URL, RUNNER_TOKEN e labels idênticas às do runs-on.
- Mantenha o Dockerfile estável para maximizar reaproveitamento.
- Evite copiar o diretório inteiro antes das deps no Dockerfile.
Segurança e próximos passos
Montar o socket do Docker dá ao container controle total do host. Isole o host, limite quem dispara workflows e audite secrets. Avalie rodar em hosts descartáveis ou com políticas de reimage. Veja o checklist: Checklist de segurança CI/CD.
Para crescer, implemente autoscaling que cria containers do runner sob demanda a partir de webhooks/filas. Guia: auto‑escalar runners self‑hosted no GitHub Actions. Se sua pipeline também publica imagens multi‑arch, confira: Como criar imagens Docker multi‑arquitetura.
Para fundamentos de cache e boas práticas de BuildKit em CI, veja: BuildKit e cache remoto no CI.
- Use redes e firewalls específicos ao host dos runners.
- Restrinja PATs e tokens, rotacionando com frequência.
- Monitore consumo e limites com métricas do host.
Perguntas frequentes
Qual a diferença entre runner efêmero e fixo?
O runner efêmero registra, executa um único job e se remove. Não mantém estado local, reduzindo deriva e riscos. O runner fixo permanece registrado, pode acumular dependências e caches locais, o que agiliza, mas aumenta necessidade de manutenção e hardening.
O cache remoto do BuildKit funciona com qualquer Dockerfile?
Funciona, porém o ganho depende de como o Dockerfile foi escrito. Multistage, ordem de COPY e uso de mounts de cache ajudam muito. Se camadas mudam sempre, o cache terá pouco efeito. Ajuste o Dockerfile para maximizar reaproveitamento.
Preciso usar registry privado para cache remoto?
Não. Você pode usar o backend type=gha, que grava o cache no próprio GitHub Actions. Registries privados são úteis para equipes que já o utilizam e desejam reuso de cache entre projetos e fora do GitHub.
É seguro montar o socket do Docker no runner?
Traz riscos, pois concede controle de root no host. Use host dedicado, restrinja quem pode executar pipelines, limite escopos de tokens e monitore. Alternativas incluem Docker-in-Docker com isolamento extra, porém com custo de performance e complexidade.
Como obter o token de registro do runner?
Gere via API do GitHub com um Personal Access Token e chame o endpoint de criação de token de runner para o repositório (ou organização). O token expira rápido, então busque-o pouco antes de iniciar o container do runner.
Composer deve cachear vendor ou o cache interno?
Prefira o cache interno do Composer (~/.cache/composer) via actions/cache. Ele acelera downloads sem arriscar inconsistências. Cache de vendor pode ser útil, mas requer chaves por SO/PHP/extensões e pode invalidar com pequenas mudanças.
Quer adaptar esse setup ao seu pipeline PHP?
Podemos revisar seu Dockerfile, workflow e estratégia de cache para reduzir o tempo de build com segurança.
Falar sobre CI com runners efêmerosVeja também
- tutorial: configurar mTLS entre Traefik e serviços em Docker Compose usando smallstep CA e certificados automáticos — passo a passo
- Tutorial: integrar Sentry ao pipeline de deploy com GitHub Actions para upload automático de source maps e criação de issues (PHP e JavaScript) — passo a passo
- Configurar retenção e compactação de logs de containers Docker com journald + logrotate + rsyslog em VPS com pouco disco
- tutorial: configurar GitHub Actions para testes de integração com PostgreSQL em containers efêmeros e rollback automático de migrações — guia prático