This document describes how to perform releases for the Docker Go SDK project.
The Docker Go SDK is a multi-module Go project organized as a workspace. Each module is versioned and released independently, but releases are typically coordinated across all modules.
Each module's version is defined in the version.go file at the root of the module.
Releases follow a two-phase process that uses pull requests instead of direct pushes to main:
- Phase 1 — Prepare Release PR: A workflow bumps versions and creates a PR with all changes.
- Phase 2 — Auto-Tag on Merge: When the PR is merged to
main, tags are automatically created on the merge commit and the Go proxy is notified.
This ensures that main always reflects released versions, tags point to reachable commits, and all changes go through code review.
- Go to the Actions tab in the GitHub repository
- Select the "Release" workflow
- Click "Run workflow"
- Configure release parameters:
- Module: Leave empty to release all modules, or enter a module name (e.g.,
client,container) - Bump Type:
prerelease(default),patch,minor, ormajor - Dry Run:
true(default) — preview changes without creating a PR
- Module: Leave empty to release all modules, or enter a module name (e.g.,
- First Run (Dry Run): Always start with
dry_run: trueto preview version changes - Review Output: Check the workflow logs for version increments
- Create PR: If satisfied, run again with
dry_run: falseto create the release PR
The workflow will:
- Create a
release/bump-*branch - Run
pre-release.shfor the target module(s) - Commit all
version.go,go.mod, andgo.sumchanges - Push the branch and create a PR with the
chorelabel
Use the same "Release" workflow but enter the module name in the Module field:
Module: container
Bump Type: patch
Dry Run: false
The module name is validated against the modules in go.work.
When the requested module is a dependency of other modules in this repository, those consumer modules are bumped in the same PR (transitively). For example, requesting a release of image will also bump and tag container, because container/go.mod requires image.
This is required for consistency: pre-release.sh rewrites the go.mod of every consumer to reference the new dependency version. If those consumers were not also bumped, main would carry rewritten go.mod content while their existing tag still pointed at the old dependency — producing two different module contents under the same version string.
When the expansion pulls in additional modules, the PR title switches from chore(<module>): bump version to chore(release): bump module versions so Phase 2's commit-message check still recognizes it as a release commit. The PR body lists every bumped module.
Modules with no in-repo consumers (e.g., container, volume, legacyadapters) release as a single-module bump with no fan-out.
prepare-release-pr.sh is the single entry point for both previewing and creating a release. It defaults to DRY_RUN=true — opt in with DRY_RUN=false to actually create the PR.
# Preview (default) — works on any branch, any fork, no origin setup required.
./.github/scripts/prepare-release-pr.sh client # one module + its consumers
./.github/scripts/prepare-release-pr.sh # all modules
# Real run — requires origin to point to docker/go-sdk and a clean main.
DRY_RUN=false ./.github/scripts/prepare-release-pr.sh client
DRY_RUN=false ./.github/scripts/prepare-release-pr.sh
# Different bump types:
BUMP_TYPE=patch DRY_RUN=false ./.github/scripts/prepare-release-pr.sh clientThe script:
- Validates the requested module exists in
go.work. - Real run only — validates
originpoints todocker/go-sdk, verifies you're onmainwith a clean working tree, and fetchesorigin/mainto confirm you're up to date. - Computes the modules to release (the requested module plus its in-repo consumers).
- Runs
pre-release.shfor each module. - Dry run — prints a version summary and exits.
- Real run — creates a release branch, commits, pushes, and opens a PR.
Phase 2 runs automatically when a push to main modifies any */version.go file (i.e., when a release PR is merged).
Phase 2 has two layers of protection to prevent accidental tagging:
- Path filter: The workflow only triggers on pushes that modify
*/version.gofiles. - Commit message check: The tagging step only proceeds if the commit message matches the release pattern produced by
prepare-release-pr.sh(chore(release): bump module versionsorchore(<module>): bump version). This prevents non-release PRs that happen to touchversion.gofrom creating tags.
The commit message check works with all merge strategies:
- Squash merge: PR title becomes the commit subject — matches directly
- Regular merge: PR title appears in the merge commit body — matched by grep
- Rebase merge: Original commit message is preserved — matches directly
For each module:
- Reads the version from
version.go - Checks if tag
<module>/v<version>already exists on the remote - Creates the tag on HEAD (the merge commit) if it doesn't exist
- Pushes each tag individually
- Triggers the Go proxy to index the new version
- No dependency on
.build/files — derives everything fromversion.govs existing git tags - Idempotent — existing tags are skipped; safe to re-run
- Squash-merge safe — tags the merge commit, not the original branch commit
If Phase 2 fails or you need to re-tag manually, you can run tag-release.sh directly. Your origin remote must point to docker/go-sdk:
# Tag all modules (from the repository root, on main)
DRY_RUN=false make tag-release
# Tag a specific module
cd client
DRY_RUN=false make tag-releaseThe script is idempotent — it skips tags that already exist.
DRY_RUN:true(default) orfalsetrue: Shows what would be done without making any changesfalse: Creates commits, PRs, tags, etc.
BUMP_TYPE:prerelease(default),patch,minor, ormajor- Controls how the version number is incremented
- Read more about semver here
- Purpose: Development versions, testing, early access
- Version Format:
v0.1.0-alpha001,v0.1.0-alpha002, etc. - Naming: Uses 3-digit zero-padded increments
- Stability: No API stability guarantees
- Purpose: Bug fixes, security updates
- Version Format:
v0.1.0→v0.1.1 - Compatibility: Backwards compatible
- Purpose: New features, backwards compatible changes
- Version Format:
v0.1.0→v0.2.0 - Compatibility: Backwards compatible
- Purpose: Breaking changes, major API changes
- Version Format:
v0.1.0→v1.0.0 - Compatibility: May include breaking changes
If tags were pushed but main doesn't contain the version bump commit:
- Create a PR from the branch/commit that has the version changes, targeting
main - Merge the PR
- Phase 2 fires, sees the tags already exist, and skips them (idempotent)
- State is now consistent:
mainhas the version changes, tags exist
Do NOT delete existing tags — the Go proxy has already indexed them and the community may depend on them.
tag-release.sh always — and prepare-release-pr.sh when run with DRY_RUN=false — validate that origin points to docker/go-sdk. If you see:
❌ Error: Git remote 'origin' points to the wrong repository
Fix it:
git remote set-url origin git@github.com:docker/go-sdk.gitIf Phase 2 fails or you need to re-tag:
# From the repository root on main
DRY_RUN=false make tag-release
# Or for a specific module
cd client
DRY_RUN=false make tag-release- Ensure you're running from the repository root
- Check that all modules exist and have
version.gofiles
- Verify git is configured with push permissions
- Check GitHub token has appropriate permissions
- Verify Docker is installed and accessible
- Check that semver-tool image can be pulled:
docker pull mdelapenya/semver-tool:3.4.0
- Ensure Go is installed and configured
- Check that all modules compile independently
- Ensure
ghCLI is installed and authenticated (gh auth status) - Check that the
chorelabel exists in the repository
- Check GitHub Actions logs for detailed error messages
- Review this document for common issues
- Examine shell scripts in
.github/scripts/for implementation details
- Always dry run first — Use
dry_run: trueto verify changes - Test before releasing — Ensure all tests pass
- Review the release PR — Check version increments and dependency updates
- Monitor after release — Check that modules are available on pkg.go.dev
- GitHub Actions are pinned to specific commit SHAs
- Secrets are handled through GitHub's secure environment
- All operations are logged and auditable
- Dry run mode prevents accidental releases
- All version changes go through PR review before tagging
- Origin remote is validated to prevent pushing to wrong repository