Install
$ agentstack add skill-brpaz-agent-skills-github-actions-docker ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
GitHub Actions Docker - Build, Smoke Test, and Publish Images
Use this skill when setting up GitHub Actions workflows for Docker image CI/CD.
Use Alongside
dockerfilefor image design, caching, and Dockerfile hardeningdocker-composefor deeper Compose service modeling and health-check guidance
When to Use
- Creating a GitHub Actions workflow that builds Docker images
- Migrating from ad-hoc
docker build && docker pushpipelines - Adding multi-platform publishing by default
- Running smoke tests against a loaded image before pushing
- Testing multi-service applications with
docker-compose.ci.yml - Keeping CI and local validation aligned through
scripts/ci.sh
Default Stance
- Use the official Docker actions stack:
actions/checkout@v6docker/setup-qemu-action@v4docker/setup-buildx-action@v4docker/metadata-action@v6docker/login-action@v4docker/build-push-action@v7- Prefer multi-platform by default for published images:
linux/amd64,linux/arm64 - Use a test-before-push flow:
- build a test image
load: true- run smoke tests
- run the final push build
- Prefer
docker/metadata-actionfor all tags and OCI labels instead of hand-rolled shell tag logic - Use Buildx caching in every workflow; prefer
cache-to/cache-from: type=ghafor simple GitHub-hosted CI, or registry cache when you want stronger cross-runner reuse - Push only outside pull requests
- Set
provenance: mode=maxexplicitly for production pushes and enablesbom: truewhen publishing production images - For multi-service apps, keep CI dependencies in
docker-compose.ci.ymland non-secret defaults in.env.ci - Put the same validation steps in
scripts/ci.shso developers can run the same flow locally
Why This Pattern
This skill prefers a two-build workflow because it gives fast validation without giving up proper multi-platform publishing:
- the first build creates a locally loaded image for smoke tests
- the second build performs the final multi-platform push
- Docker documents that the later build can reuse cache, so the second build is mostly paying for the additional platform work rather than rebuilding everything from scratch
Recommended Action Order
When multi-platform support is enabled:
actions/checkoutdocker/setup-qemu-actiondocker/setup-buildx-actiondocker/login-action(only when push is possible)docker/metadata-actiondocker/build-push-actionfor test image (load: true, single-platform)- smoke tests
docker/build-push-actionfor final publish (push: true, multi-platform)
setup-qemu-action should run before setup-buildx-action when QEMU is needed.
Recommended Repository Layout
.github/
workflows/
docker.yml
docker-compose.ci.yml
.env.ci
scripts/
ci.sh
Dockerfile
.dockerignore
Core Workflow Pattern
name: docker
on:
pull_request:
push:
branches: [main]
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
PLATFORMS: linux/amd64,linux/arm64
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Compute short SHA
id: vars
shell: bash
run: echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ github.event_name == 'release' }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build smoke-test image
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
load: true
tags: local/test-image:sha-${{ steps.vars.outputs.short_sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run shared CI script
env:
IMAGE_TAG: local/test-image:sha-${{ steps.vars.outputs.short_sha }}
run: ./scripts/ci.sh
- name: Build and push multi-platform image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max
sbom: true
Required Workflow Behavior
- Pull requests: build, load, smoke test, run CI checks, do not push
- Default branch: build, smoke test, run CI checks, then push multi-platform image
- Published releases: use the
release.publishedevent and metadata-derived version tags rather than tag-push triggers
Tagging and Metadata
Prefer docker/metadata-action for:
- branch tags
- PR tags
- release-driven semver tags
- SHA tags
- OCI labels
- annotations passed to the final publish build
Do not hand-maintain separate shell logic for every tag type unless you have a very unusual release policy.
Multi-Platform Guidance
Default published platforms:
platforms: linux/amd64,linux/arm64
Guidance:
- Use
linux/amd64for the loaded smoke-test image unless you have a specific reason to test another platform locally in CI - Keep the final publish step multi-platform
- If you need to load a multi-platform image into the local Docker store, that requires extra Docker daemon setup with the containerd image store; do not add that complexity unless it solves a real need
- For very heavy builds, consider Docker's newer distributed builder workflow patterns, but keep the simple single-job Buildx flow as the default
Smoke Test Guidance
Smoke tests should verify the container can boot and serve its most critical behavior. Good examples:
- process starts successfully
- health endpoint returns success
- CLI image responds to
--helporversion - web app can boot with CI config
Avoid turning smoke tests into your entire integration suite.
For service-style applications, prefer starting the app in the Compose CI stack and running smoke.sh against the running application instead of launching a separate docker run just for smoke testing.
Example scripts/smoke.sh:
#!/usr/bin/env bash
set -euo pipefail
curl --fail --show-error --silent http://127.0.0.1:8080/healthz >/dev/null
docker-compose.ci.yml Pattern
For multi-service applications, prefer bringing the app container up inside the Compose CI stack alongside its dependencies, then run smoke and test scripts against the running application.
Use docker-compose.ci.yml for supporting services such as:
- Postgres
- Redis
- MinIO
- Mailpit
- OpenSearch
If scripts/ci.sh already starts and stops Compose dependencies, do not duplicate docker compose up/down in the workflow.
Example:
services:
postgres:
image: postgres:16-alpine
env_file:
- .env.ci
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 20
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20
app:
image: "${IMAGE_TAG}"
env_file:
- .env.ci
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/healthz || exit 1"]
interval: 5s
timeout: 5s
retries: 20
If the image does not contain curl, use an app-appropriate healthcheck command instead of forcing curl into the image just for CI.
.env.ci Pattern
Use .env.ci for non-secret CI defaults that should also work locally.
Example:
APP_ENV=ci
LOG_LEVEL=debug
PORT=8080
POSTGRES_DB=app_test
POSTGRES_USER=app
POSTGRES_PASSWORD=app
DATABASE_URL=postgres://app:app@postgres:5432/app_test?sslmode=disable
REDIS_URL=redis://redis:6379/0
Rules:
- keep
.env.cinon-secret - use GitHub Actions secrets for real credentials
- use Compose
env_filefor convenience, not for secret management - make local and CI defaults match unless there is a strong reason not to
Health Checks and Readiness
Do not rely on bare depends_on for readiness. In CI stacks:
- add explicit health checks for Postgres, Redis, and other dependencies
- use
depends_on: condition: service_healthywhen Compose is orchestrating test dependencies - prefer
docker compose ... up -d --wait --wait-timeoutso Compose waits for healthchecks to pass before smoke or integration tests start - fail fast when a dependency never becomes healthy
scripts/ci.sh Pattern
Put the real validation logic in scripts/ci.sh, then let both GitHub Actions and local developers call the same script.
This prevents your workflow YAML from becoming the only source of truth.
Example:
#!/usr/bin/env bash
set -euo pipefail
IMAGE_TAG="${IMAGE_TAG:-}"
BUILD_IMAGE="${BUILD_IMAGE:-0}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.ci.yml}"
ENV_FILE="${ENV_FILE:-.env.ci}"
if [[ -z "$IMAGE_TAG" ]]; then
printf 'IMAGE_TAG is required\n' >&2
exit 1
fi
if [[ "$BUILD_IMAGE" == "1" ]]; then
docker buildx build \
--load \
--platform linux/amd64 \
-t "$IMAGE_TAG" \
.
fi
export IMAGE_TAG
cleanup() {
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" down -v || true
}
trap cleanup EXIT
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d --wait --wait-timeout 120
./scripts/smoke.sh
./scripts/test.sh
Recommended properties:
- executable and documented
- deterministic
- safe to run repeatedly
- owns cleanup via
trap - validates required env vars up front, especially
IMAGE_TAG - does not build by default; only builds when
BUILD_IMAGE=1 - accepts configurable
COMPOSE_FILEandENV_FILE, defaulting todocker-compose.ci.ymland.env.ci - starts the Compose stack with
--wait --wait-timeoutso healthchecks gate smoke and test execution - uses the same
.env.cianddocker-compose.ci.ymlfiles as GitHub Actions
Cache Guidance
Simple default
For GitHub-hosted runners:
cache-from: type=gha
cache-to: type=gha,mode=max
When to prefer registry cache
Prefer registry cache when:
- you want cache reuse across more workflows and runners
- builds are large and frequent
- you need stronger reuse than GitHub cache gives you
Example:
cache-from: type=registry,ref=ghcr.io/acme/app:buildcache
cache-to: type=registry,ref=ghcr.io/acme/app:buildcache,mode=max
Provenance, SBOM, and Secrets
For production pushes:
provenance: mode=max
sbom: true
Rules:
- do not expect provenance or SBOM output from the
load: truesmoke-test build - use secret mounts in the Dockerfile for build secrets
- do not pass secrets as plain build args because they can leak into build records and attestations
Registry Login Guidance
Prefer explicit login steps per registry.
Examples:
GHCR
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
Docker Hub
- uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Use tokens, not passwords.
Local vs CI Contract
The contract should be:
- GitHub Actions orchestrates runner setup, auth, metadata, and publish
scripts/ci.showns the actual validation sequencescripts/ci.shshould requireIMAGE_TAGrather than silently defaulting itscripts/ci.shshould not build by default; setBUILD_IMAGE=1only when the script itself must perform the image buildscripts/ci.shshould acceptCOMPOSE_FILEandENV_FILEoverrides while defaulting todocker-compose.ci.ymland.env.ci- if the workflow pre-builds and loads the smoke-test image, pass
IMAGE_TAGand letBUILD_IMAGEstay unset - use a short-SHA test image tag to reduce collisions between runs
.env.cianddocker-compose.ci.ymlare shared between local and CI execution
If the local script and workflow drift apart, fix the drift instead of adding more one-off workflow shell logic.
Review Checklist
- Uses
actions/checkout,docker/setup-qemu-action,docker/setup-buildx-action,docker/metadata-action,docker/login-action, anddocker/build-push-action - Uses
docker/metadata-actionoutputs for tags and labels - Builds a single-platform image with
load: truebefore CI validation - Runs smoke tests against the running application before any push
- Uses a short-SHA test image tag to avoid collisions between runs
- Allows
scripts/ci.shcallers to override compose/env file paths when needed - Publishes multi-platform images by default
- Does not push on pull requests
- Uses caching
- Uses
.env.ci,docker-compose.ci.yml, andscripts/ci.sh - Adds health checks for Postgres/Redis and other dependencies
- Keeps secrets out of
.env.ci - Uses explicit provenance/SBOM settings on production pushes
Anti-Patterns
- Building and pushing before any smoke test
- Hand-rolling Docker tag logic instead of using
docker/metadata-action - Treating PR builds and release builds as completely different validation paths
- Storing secrets in
.env.ci - Using Compose
depends_onwithout health checks and calling the stack “ready” - Duplicating the whole CI sequence in YAML instead of centralising it in
scripts/ci.sh - Starting and stopping Compose both in the workflow and inside
scripts/ci.sh - Letting
scripts/ci.shguess a defaultIMAGE_TAGinstead of failing fast when it is missing - Making image build the default path instead of an explicit opt-in
- Hardcoding the compose/env file paths inside
scripts/ci.shwhen simple env overrides would make it reusable - Starting a separate
docker runsmoke container when the app is already brought up via Compose - Reusing a fixed smoke-test image tag across concurrent CI runs
- Publishing only
linux/amd64by default when the image is meant for general use - Loading and testing a multi-platform image locally when a single-platform smoke-test image is enough
Quick Template
- uses: actions/checkout@v6
- id: vars
run: echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- uses: docker/metadata-action@v6
id: meta
with:
images: ghcr.io/${{ github.repository }}
- uses: docker/build-push-action@v7
with:
platforms: linux/amd64
load: true
tags: local/test-image:sha-${{ steps.vars.outputs.short_sha }}
- run: IMAGE_TAG=local/test-image:sha-${{ steps.vars.outputs.short_sha }} ./scripts/ci.sh
- uses: docker/build-push-action@v7
if: github.event_name != 'pull_request'
with:
platforms: linux/amd64,linux/arm64
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [brpaz](https://github.com/brpaz)
- **Source:** [brpaz/agent-skills](https://github.com/brpaz/agent-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.