A composite GitHub Action that runs Go unit tests and builds + pushes a Docker image to a local in-job registry — in a single step.
It replaces the shared CI prelude every Go-based Docker action repo tends to copy (unit-tests + build-and-push-docker jobs with services: registry:2, setup-buildx, docker/build-push-action).
- One action, whole Go/Docker prelude:
setup-go→ unit tests (with optional coverage threshold) → start localregistry:2→setup-buildx→docker/build-push-action→ push tolocalhost:<port>/actions/<name>:<tag> - Defaults match the standard Go Docker action layout (
go.mod,./...tests with coverage, single-archDockerfilebuild,:latesttag, registry on port5001) — zero config for most repos - Tunable: pinned
go_version, subdirectoryworking_directory,cache_dependency_pathpassthrough for mono-repos, customtest_command,coverage_threshold, customimage_name/image_tag/build_args/dockerfile/build_context/registry_port, and an escape hatch to disable the built-in registry (manage_registry: false) services:is job-scoped and unusable inside a composite — the action runsregistry:2viadocker run -dand stops it onif: always(). Callers who already declareservices: registrycan passmanage_registry: falseto reuse it.- Writes a per-run summary table to
$GITHUB_STEP_SUMMARY - Exposes
image_ref,image_digest, andtest_exit_codeoutputs for downstream pinning and smoke steps
- Runner OS:
ubuntu-latestis the tested target (Docker daemon must be available fordocker run registry:2anddocker/build-push-action). GitHub-hosted Ubuntu runners ship both. - Caller must run
actions/checkoutbefore this action so thatworking_directorycontains the Go module andbuild_contextcontains theDockerfile. - Go toolchain for
setup-go— resolved from the caller'sgo.modby default, or pinned viago_version.
Drop this into .github/workflows/ci.yml of any Go-based Docker action repo:
name: Continuous Integration
on:
pull_request:
push:
branches: [main]
paths-ignore:
- '.github/workflows/**'
- '**/*.md'
workflow_dispatch:
permissions:
contents: read
jobs:
ci-prelude:
name: Unit Tests + Docker Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1With all defaults it runs: setup-go from go.mod → go test ./... -v -cover -coverprofile=coverage.out → start registry:2 on localhost:5001 → setup-buildx → docker/build-push-action@v7 with push: true and tags: localhost:5001/actions/<repo-name>:latest. The registry is stopped on if: always().
Downstream smoke test jobs can then set needs: ci-prelude and consume the image reference or just uses: ./ the action against the repository's action.yml.
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
go_version: '1.26'- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
coverage_threshold: '80'The default test_command already emits coverage.out. If you override test_command, make sure it still writes coverage.out (e.g., include -coverprofile=coverage.out) so the threshold step can parse it.
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
run_unit_tests: 'false'Useful when unit tests live in a dedicated job and you only need the "build + push to local registry" prelude.
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
test_command: 'go test ./internal/... -v -cover -coverprofile=coverage.out'- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
working_directory: action
dockerfile: Dockerfile
build_context: actionworking_directory controls where Go tests run; build_context controls the Docker build context. Both typically point at the same directory.
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
working_directory: services/api
cache_dependency_path: services/api/go.sum- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
image_name: my-action
registry_port: '5050'
dockerfile: docker/Dockerfile
build_context: .- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
image_tag: sha-${{ github.sha }}The resulting image_ref is localhost:5001/actions/<repo>:sha-<SHA>. Downstream jobs pin pulls to that ref (or to the image_digest output for true immutability).
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
build_args: |
GIT_SHA=${{ github.sha }}
BUILD_VERSION=${{ github.ref_name }}Each KEY=VALUE line is forwarded verbatim as a --build-arg to the Dockerfile. The action does not interpret values beyond passthrough.
jobs:
ci-prelude:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5001:5000
steps:
- uses: actions/checkout@v6
- uses: somaz94/go-docker-action-ci-action@v1
with:
manage_registry: 'false'manage_registry: false makes the action skip docker run registry:2 / cleanup. The action still builds and pushes to localhost:<registry_port>/actions/<image_name>:latest, so the caller's services: port and the action's registry_port must agree.
- id: ci
uses: somaz94/go-docker-action-ci-action@v1
- name: Report and pull pinned by digest (requires manage_registry=false + services.registry)
if: always()
run: |
echo "image_ref=${{ steps.ci.outputs.image_ref }}"
echo "image_digest=${{ steps.ci.outputs.image_digest }}"
echo "test_exit_code=${{ steps.ci.outputs.test_exit_code }}"image_refis always set (e.g.,localhost:5001/actions/my-action:latest).image_digestis the immutablesha256:...produced bydocker/build-push-action— use for pinned downstream pulls (localhost:<port>/actions/<name>@<digest>).test_exit_codeis0when tests passed, and empty whenrun_unit_tests: false.
When
manage_registry: true(default), the action stops the in-job registry right after the build — downstream pulls must happen via a follow-up job that declaresservices: registryand the action is called withmanage_registry: false. Otherwise useimage_digestonly as an identifier emitted into the workflow summary.
| Input | Description | Required | Default |
|---|---|---|---|
go_version_file |
Path to go.mod (or another file) used by actions/setup-go as go-version-file. Ignored when go_version is set. |
No | go.mod |
go_version |
Explicit Go version (e.g., 1.26). Takes precedence over go_version_file when non-empty. |
No | '' |
cache |
Enable Go module/build cache in actions/setup-go. |
No | true |
cache_dependency_path |
Passthrough to actions/setup-go cache-dependency-path. Leave empty to rely on setup-go's default (go.sum next to go.mod). Handy for mono-repos. |
No | '' |
working_directory |
Directory to run unit tests and resolve the build context from (Go module root). | No | . |
run_unit_tests |
When true (default), run test_command before the Docker build. Set false to skip the test phase entirely. |
No | true |
test_command |
Shell command executed from working_directory for unit tests. Must produce coverage.out when coverage_threshold is non-empty. |
No | go test ./... -v -cover -coverprofile=coverage.out |
coverage_threshold |
Minimum total coverage percent (e.g., 80). Leave empty to skip the threshold check. |
No | '' |
image_name |
Image name used in the final tag localhost:<registry_port>/actions/<image_name>:<image_tag>. Empty derives from the current repository name. |
No | '' |
image_tag |
Tag appended to the pushed image reference (e.g., latest, sha-abc123, ${{ github.sha }}). Applied verbatim — must be a valid Docker tag. |
No | latest |
dockerfile |
Path to the Dockerfile, relative to build_context. |
No | Dockerfile |
build_context |
Build context passed to docker/build-push-action. |
No | . |
build_args |
Multi-line list of build-time variables passed through to docker/build-push-action's build-args (one KEY=VALUE per line). |
No | '' |
registry_port |
Host port exposed by the local registry:2 container. Final tag is localhost:<port>/actions/<image_name>:<image_tag>. |
No | 5001 |
manage_registry |
When true (default), run a local registry:2 container from the action and stop it after the build. Set false to rely on a caller-managed services: registry on the same port. |
No | true |
| Output | Description |
|---|---|
image_ref |
Full image reference that was built and pushed (e.g., localhost:5001/actions/my-action:latest). |
image_digest |
Image digest (sha256:...) emitted by docker/build-push-action. Use for pinned downstream pulls (<host>/<repo>@<digest>). |
test_exit_code |
Exit code of the unit test command. 0 when tests passed. Empty when run_unit_tests is false. |
The action itself needs no special permissions beyond what actions/checkout and actions/setup-go require. A typical caller:
permissions:
contents: read- Validate inputs —
go_versionorgo_version_filemust be set;working_directoryandbuild_contextmust exist;dockerfilemust be a file insidebuild_context;test_commandmust be non-empty whenrun_unit_tests: true;coverage_thresholdmust be numeric when set;registry_portmust be a positive integer;image_tagmust be a valid Docker tag. actions/setup-go— either fromgo_version_file(defaultgo.mod) orgo_version(when explicitly set). Go module/build cache controlled bycache;cache_dependency_pathis passed through verbatim.- Resolve image metadata — derives the final
image_refaslocalhost:<registry_port>/actions/<image_name>:<image_tag>. Whenimage_nameis empty, uses${{ github.repository }}'s name component (e.g.,my-actionforsomaz94/my-action). Emits bothimage_nameandimage_refas step outputs (image_refis the action's output). - Run unit tests (when
run_unit_tests: true) —bash -c "$test_command"fromworking_directory. Whencoverage_thresholdis set, parsescoverage.outviago tool cover -funcand fails the action if total coverage is below the threshold. Emitstest_exit_code=0on success. - Start local registry (when
manage_registry: true) — reuses an existinggo-docker-ci-action-registrycontainer if running; otherwisedocker run -d --rm -p <port>:5000 --name go-docker-ci-action-registry registry:2. Pollshttp://localhost:<port>/v2/for up to 15 seconds until ready. docker/setup-buildx-action@v4— installs buildx withdriver-opts: network=hostso the in-job registry is reachable vialocalhost.- Configure Git safe directory —
git config --global --add safe.directory "$GITHUB_WORKSPACE", matching the pattern inline Go action CI workflows already use. docker/build-push-action@v7—context: build_context,file: build_context/dockerfile,push: true,tags: <image_ref>,build-args: <build_args>. The local registry receives the push. The digest returned by buildx is exposed as theimage_digestoutput.- Stop local registry (
if: always()whenmanage_registry: true) —docker stopremoves theregistry:2container (started with--rm). Always runs so the container doesn't leak even on test failure. Whenmanage_registry: true, downstream steps in the same job cannot pull from the registry — it's gone by the time the action returns. - Summary — a markdown table (working directory / unit tests / coverage threshold / dockerfile / registry / image ref / image digest when available) is appended to
$GITHUB_STEP_SUMMARY.
This project is licensed under the MIT License — see the LICENSE file for details.