Release Automation
Release Automation Overview
Automating releases ensures consistency, reduces errors, and saves time. GoReleaser and GitHub Actions provide complete automation for Terraform provider releases.
GoReleaser
GoReleaser builds, archives, checksums, and publishes Go projects.
Installation
# macOS
brew install goreleaser/tap/goreleaser
# Linux
curl -sfL https://goreleaser.com/static/run | sh
# Verify installation
goreleaser --version
Basic Configuration
Create .goreleaser.yml:
# .goreleaser.yml
version: 2
before:
hooks:
# Ensure dependencies are tidy
- go mod tidy
# Generate any code
- go generate ./...
builds:
- env:
# Disable CGO for static binaries
- CGO_ENABLED=0
mod_timestamp: '{{ .CommitTimestamp }}'
flags:
- -trimpath
ldflags:
- '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}'
goos:
- linux
- windows
- darwin
goarch:
- amd64
- '386'
- arm
- arm64
ignore:
- goos: darwin
goarch: '386'
- goos: windows
goarch: arm64
- goos: windows
goarch: arm
binary: '{{ .ProjectName }}_v{{ .Version }}'
archives:
- format: zip
name_template: '{{ .ProjectName }}_{{ .Version}}_{{ .Os }}_{{ .Arch }}'
checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
algorithm: sha256
signs:
- artifacts: checksum
args:
- "--batch"
- "--local-user"
- "{{ .Env.GPG_FINGERPRINT }}"
- "--output"
- "${signature}"
- "--detach-sign"
- "${artifact}"
release:
draft: false
prerelease: auto
changelog:
sort: asc
use: github
filters:
exclude:
- '^docs:'
- '^test:'
- Merge pull request
- Merge branch
groups:
- title: Features
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: 'Bug fixes'
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Others
order: 999
Testing Locally
# Test release without publishing
goreleaser release --snapshot --clean
# Check dist/ directory
ls -la dist/
# Verify archives
unzip -l dist/terraform-provider-cachecloud_1.0.0_darwin_amd64.zip
GitHub Actions Workflow
Release Workflow
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: $
passphrase: $
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GPG_FINGERPRINT: $
GITHUB_TOKEN: $
Test Workflow
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- run: go mod download
- run: go build -v .
generate:
name: Go Generate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- run: go generate ./...
- name: git diff
run: |
git diff --compact-summary --exit-code || \
(echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1)
test:
name: Terraform Provider Acceptance Tests
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
terraform:
- '1.5.*'
- '1.6.*'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: $
terraform_wrapper: false
- run: go mod download
- env:
TF_ACC: "1"
run: go test -v -cover ./internal/provider/
timeout-minutes: 10
GPG Key Management
Generate GPG Key
# Generate key
gpg --full-generate-key
# Select options:
# - Kind: RSA and RSA
# - Size: 4096 bits
# - Expiration: 2 years
# - Name: Your Name
# - Email: your.email@example.com
# - Passphrase: (strong passphrase)
Export Keys
# List keys
gpg --list-secret-keys --keyid-format=long
# Output:
# sec rsa4096/ABCD1234EFGH5678 2026-01-01 [SC] [expires: 2028-01-01]
# uid [ultimate] Your Name <your.email@example.com>
# Export private key (for GitHub Secrets)
gpg --armor --export-secret-keys ABCD1234EFGH5678 > private-key.asc
# Export public key (for Terraform Registry)
gpg --armor --export ABCD1234EFGH5678 > public-key.asc
# Get fingerprint
gpg --fingerprint ABCD1234EFGH5678
Store in GitHub Secrets
Settings → Secrets and variables → Actions → New repository secret
GPG_PRIVATE_KEY: (contents of private-key.asc)
PASSPHRASE: (your GPG passphrase)
Multi-Platform Builds
Supported Platforms
# .goreleaser.yml
builds:
- goos:
- linux
- windows
- darwin
- freebsd
goarch:
- amd64
- 386
- arm
- arm64
goarm:
- 6
- 7
ignore:
- goos: darwin
goarch: 386
- goos: darwin
goarch: arm
- goos: windows
goarch: arm
Binary Naming
binary: '{{ .ProjectName }}_v{{ .Version }}'
Generates:
terraform-provider-cachecloud_v1.0.0
Archive Naming
archives:
- format: zip
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
Generates:
terraform-provider-cachecloud_1.0.0_linux_amd64.zip
terraform-provider-cachecloud_1.0.0_darwin_amd64.zip
terraform-provider-cachecloud_1.0.0_windows_amd64.zip
Checksums and Signatures
Checksums
checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
algorithm: sha256
Generates:
terraform-provider-cachecloud_1.0.0_SHA256SUMS
Content:
a1b2c3d4... terraform-provider-cachecloud_1.0.0_linux_amd64.zip
e5f6g7h8... terraform-provider-cachecloud_1.0.0_darwin_amd64.zip
i9j0k1l2... terraform-provider-cachecloud_1.0.0_windows_amd64.zip
Signatures
signs:
- artifacts: checksum
args:
- "--batch"
- "--local-user"
- "{{ .Env.GPG_FINGERPRINT }}"
- "--output"
- "${signature}"
- "--detach-sign"
- "${artifact}"
Generates:
terraform-provider-cachecloud_1.0.0_SHA256SUMS.sig
Verification
# Verify signature
gpg --verify terraform-provider-cachecloud_1.0.0_SHA256SUMS.sig \
terraform-provider-cachecloud_1.0.0_SHA256SUMS
# Verify checksum
sha256sum -c terraform-provider-cachecloud_1.0.0_SHA256SUMS
Changelog Generation
Automatic Changelog
changelog:
sort: asc
use: github
filters:
exclude:
- '^docs:'
- '^test:'
- 'Merge pull request'
- 'Merge branch'
groups:
- title: Features
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: 'Bug fixes'
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: 'Enhancements'
regexp: '^.*?chore|perf|refactor(\([[:word:]]+\))??!?:.+$'
order: 2
- title: Others
order: 999
Commit Message Format
# Feature
git commit -m "feat: add backup_window attribute"
# Bug fix
git commit -m "fix: correct cache deletion timeout"
# Enhancement
git commit -m "chore: update dependencies"
# Breaking change
git commit -m "feat!: rename num_nodes to node_count"
Generated changelog:
## Features
* feat: add backup_window attribute
## Bug fixes
* fix: correct cache deletion timeout
## Enhancements
* chore: update dependencies
## Breaking Changes
* feat!: rename num_nodes to node_count
Release Notes
Custom Release Notes
release:
github:
owner: example
name: terraform-provider-cachecloud
draft: false
prerelease: auto
header: |
## What's Changed
footer: |
## Installation
```terraform
terraform {
required_providers {
cachecloud = {
source = "example/cachecloud"
version = "{{ .Version }}"
}
}
}
```
**Full Changelog**: https://github.com/example/terraform-provider-cachecloud/compare/{{ .PreviousTag }}...{{ .Tag }}
Complete Example
Project Structure
terraform-provider-cachecloud/
├── .github/
│ └── workflows/
│ ├── release.yml
│ └── test.yml
├── .goreleaser.yml
├── internal/
│ └── provider/
├── main.go
├── go.mod
└── go.sum
.goreleaser.yml
version: 2
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
mod_timestamp: '{{ .CommitTimestamp }}'
flags:
- -trimpath
ldflags:
- '-s -w -X main.version={{.Version}}'
goos:
- freebsd
- windows
- linux
- darwin
goarch:
- amd64
- '386'
- arm
- arm64
ignore:
- goos: darwin
goarch: '386'
- goos: windows
goarch: arm64
binary: '{{ .ProjectName }}_v{{ .Version }}'
archives:
- format: zip
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
checksum:
extra_files:
- glob: 'terraform-registry-manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
algorithm: sha256
signs:
- artifacts: checksum
args:
# if you are using this in a GitHub action or some other automated pipeline, you
# need to pass the batch flag to indicate its not interactive.
- "--batch"
- "--local-user"
- "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key
- "--output"
- "${signature}"
- "--detach-sign"
- "${artifact}"
release:
extra_files:
- glob: 'terraform-registry-manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
changelog:
use: github
sort: asc
abbrev: 0
groups:
- title: Features
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: 'Bug Fixes'
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: 'Enhancements'
regexp: '^.*?chore(\([[:word:]]+\))??!?:.+$'
order: 2
- title: Others
order: 999
filters:
exclude:
- '^docs:'
- '^test:'
Testing the Release Process
Dry Run
# Test without creating release
goreleaser release --snapshot --clean
# Check output
ls -la dist/
Local Release Test
# Set environment variables
export GPG_FINGERPRINT=$(gpg --list-secret-keys --keyid-format=long | grep sec | awk '{print $2}' | cut -d'/' -f2)
# Test release
goreleaser release --snapshot --clean --skip=publish
# Verify archives
unzip -l dist/terraform-provider-cachecloud_*.zip
# Verify checksums
cd dist
sha256sum -c *_SHA256SUMS
Troubleshooting
Issue: GPG Signing Fails
Problem:
error: gpg: signing failed: Inappropriate ioctl for device
Solution:
# Add to ~/.bashrc or ~/.zshrc
export GPG_TTY=$(tty)
# Reload
source ~/.bashrc
Issue: Build Fails
Problem:
error: Build failed for darwin/386
Solution:
Add to ignore list in .goreleaser.yml:
ignore:
- goos: darwin
goarch: '386'
Issue: Missing Artifacts
Problem: Release created but artifacts missing
Solution: Check GitHub Actions logs:
- name: Debug
run: ls -la dist/
Key Takeaways
- ✅ Use GoReleaser for consistent releases
- ✅ Automate with GitHub Actions
- ✅ Sign releases with GPG
- ✅ Build for multiple platforms
- ✅ Generate checksums
- ✅ Auto-generate changelogs
- ✅ Test releases locally first
- ✅ Store GPG keys securely
- ✅ Follow commit message conventions
- ✅ Monitor release workflow
What’s Next?
Learn about signing and security best practices for provider releases.
Previous: ← Versioning & Releases
Next: Signing & Security →
Last updated: June 30, 2026