ops(ci): automate testing and container image releases

Add PostgreSQL-backed Go validation for application changes and semantic-version release publishing to GHCR.

Default deployments to the public image and prevent host setup from starting the application before deployment.
This commit is contained in:
cole-maxwell1
2026-08-07 11:38:23 -05:00
parent d118f52387
commit 2ff14e6221
6 changed files with 229 additions and 8 deletions
+130
View File
@@ -0,0 +1,130 @@
name: Publish container image
on:
release:
types: [published]
permissions:
contents: read
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
jobs:
test:
name: Test release
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_DB: todo_test
POSTGRES_USER: todo_test
POSTGRES_PASSWORD: test-password
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U todo_test -d todo_test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
defaults:
run:
working-directory: app
env:
TEST_DATABASE_URL: postgres://todo_test:test-password@127.0.0.1:5432/todo_test?sslmode=disable
steps:
- name: Validate release tag
working-directory: .
run: |
if [[ ! "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$ ]]; then
printf 'Release tag must use semantic versioning with a v prefix: %s\n' "$RELEASE_TAG" >&2
exit 1
fi
- name: Check out release
uses: actions/checkout@v6
with:
ref: ${{ env.RELEASE_TAG }}
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: app/go.mod
cache-dependency-path: app/go.sum
- name: Run tests
run: go test ./...
- name: Run vet
run: go vet ./...
publish:
name: Publish image
needs: test
runs-on: ubuntu-latest
concurrency:
group: publish-image-${{ github.event.release.tag_name }}
cancel-in-progress: false
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Check out release
uses: actions/checkout@v6
with:
ref: ${{ env.RELEASE_TAG }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{raw}},value=${{ env.RELEASE_TAG }}
type=semver,pattern={{version}},value=${{ env.RELEASE_TAG }}
type=semver,pattern={{major}}.{{minor}},value=${{ env.RELEASE_TAG }}
type=semver,pattern={{major}},value=${{ env.RELEASE_TAG }},enable=${{ !startsWith(env.RELEASE_TAG, 'v0.') }}
type=raw,value=latest,enable=${{ !github.event.release.prerelease }}
labels: |
org.opencontainers.image.description=Server-rendered Go todo application deployed to Hetzner Cloud
annotations: |
org.opencontainers.image.description=Server-rendered Go todo application deployed to Hetzner Cloud
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- name: Build and push image
id: push
uses: docker/build-push-action@v6
with:
context: app
file: app/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: Attest image provenance
uses: actions/attest@v4
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true