Skip to content

Harden self-hosted release workflow (#1) #3

Harden self-hosted release workflow (#1)

Harden self-hosted release workflow (#1) #3

Workflow file for this run

name: Release
on:
push:
tags:
- "v0.*.*"
permissions:
contents: write
packages: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
validate:
name: Release-ready validation
uses: ./.github/workflows/release-validation.yml
publish:
name: Publish container and release notes
runs-on: ubuntu-latest
needs: validate
steps:
- name: Checkout release tag
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Verify release tag belongs to master
run: |
git fetch origin master:refs/remotes/origin/master
git merge-base --is-ancestor "$GITHUB_SHA" origin/master
- name: Log in to GitHub Container Registry
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: echo "$PASSWORD" | docker login "$REGISTRY" --username "$USERNAME" --password-stdin
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Determine image tags
id: versions
run: echo "minor=${GITHUB_REF_NAME%.*}" >> "$GITHUB_OUTPUT"
- name: Publish application image
id: image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
ghcr.io/${{ github.repository }}:${{ steps.versions.outputs.minor }}
ghcr.io/${{ github.repository }}:latest
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.version=${{ github.ref_name }}
cache-from: type=gha,scope=release
cache-to: type=gha,mode=max,scope=release
provenance: mode=max
sbom: true
- name: Smoke test published image
env:
IMAGE: ghcr.io/${{ github.repository }}
DIGEST: ${{ steps.image.outputs.digest }}
run: |
docker pull "$IMAGE@$DIGEST"
docker run --detach --rm --name versiontracker --publish 8080:80 \
--env APP_ENV=production \
--env APP_DEBUG=false \
--env APP_KEY=base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= \
"$IMAGE@$DIGEST"
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8080/up; then
exit 0
fi
sleep 2
done
docker logs versiontracker
exit 1
- name: Scan published image
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
with:
image-ref: ghcr.io/${{ github.repository }}@${{ steps.image.outputs.digest }}
format: table
exit-code: "1"
ignore-unfixed: true
severity: CRITICAL,HIGH
vuln-type: os,library
- name: Validate published installer path
env:
VERSION: ${{ github.ref_name }}
COMPOSE_PROJECT_NAME: versiontracker-release-install
run: |
test -x install.sh
printf '%s\n' 'Release Test Password 2026' | ./install.sh install \
--version "$VERSION" \
--mode proxy \
--port 18080 \
--admin-name 'Release Test' \
--admin-email release-test@example.invalid \
--admin-password-stdin
./install.sh status
backup_directory="$(./install.sh backup)"
test -s "$backup_directory/database.sql"
test -s "$backup_directory/storage.tar.gz"
- name: Build deployment bundle
env:
VERSION: ${{ github.ref_name }}
run: |
bundle="versiontracker-deploy-${VERSION}.tar.gz"
tar --create --gzip --file "$bundle" \
--transform "s,^,versiontracker-deploy-${VERSION}/," \
install.sh compose.yml compose.proxy.yml compose.caddy.yml Caddyfile .env.docker.example docs/self-hosting.md
sha256sum "$bundle" > "${bundle}.sha256"
- name: Clean up release installation test
if: always()
env:
COMPOSE_PROJECT_NAME: versiontracker-release-install
run: docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml down --volumes --remove-orphans
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IMAGE: ghcr.io/${{ github.repository }}
DIGEST: ${{ steps.image.outputs.digest }}
run: |
gh release create "$GITHUB_REF_NAME" \
--generate-notes \
--title "$GITHUB_REF_NAME" \
--notes $'### Container image\n\n`'"$IMAGE@$DIGEST"$'\n\n### Deployment bundle\n\nDownload the `versiontracker-deploy-` archive and verify its SHA-256 checksum before installation.\n\n### Upgrade notes\n\nSee the self-hosting guide for backup, update, and rollback instructions.' \
"versiontracker-deploy-${GITHUB_REF_NAME}.tar.gz" \
"versiontracker-deploy-${GITHUB_REF_NAME}.tar.gz.sha256"