CI - Publish and Release workflow (Nya-Foundation/NyaProxy)
The CI - Publish and Release workflow from Nya-Foundation/NyaProxy, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get caching, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the CI - Publish and Release workflow from the Nya-Foundation/NyaProxy repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
# .github/workflows/publish.yml
#
# Standard Publish Workflow for Nya Foundation
#
# This workflow handles the full CI/CD pipeline for Foundation:
# 1. Runs tests and checks
# 2. Uses semantic-release to automatically version the project based on commit messages
# 3. Publishes the package to PyPI
# 4. Builds and pushes Docker images to both Docker Hub and GitHub Container Registry
# 5. Creates GitHub releases with changelog
#
# Required Secrets:
# - CI_APP_ID: GitHub App ID for token generation
# - CI_APP_PRIVATE_KEY: GitHub App private key for token generation
# - DOCKERHUB_USERNAME: Username for Docker Hub
# - DOCKERHUB_TOKEN: Access token for Docker Hub (not password)
#
# Optional Variables (set in repository variables):
# - GCR_IMAGE_NAME: Image name for GitHub Container Registry (example: nya-foundation/nya-proxy)
# - DOCKERHUB_IMAGE_NAME: Image name for Docker Hub (example: k3scat/nya-proxy)
#
# Permissions needed:
# - contents: write (for creating GitHub releases)
# - id-token: write (for PyPI trusted publishing)
# - packages: write (for publishing to GitHub Container Registry)
name: CI - Publish and Release
on:
push:
branches:
- main # Trigger release only on pushes to main
paths:
- '**/*.py' # Trigger on changes to Python files
# Prevent multiple concurrent releases
concurrency:
group: ${{ github.workflow }}-release
cancel-in-progress: false # Do not cancel releases once started
jobs:
publish:
runs-on: ubuntu-latest
environment:
name: Publish and Release
permissions:
contents: write # Needed to create GitHub releases and tags
id-token: write # Needed for PyPI trusted publishing (OIDC) - Recommended!
packages: write # Needed for publishing Docker images
steps:
# Generate Token for the GitHub App
- name: Generate GitHub App Token
id: generate-token
# Uses App ID and Private Key secrets to generate a short-lived installation token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.CI_APP_ID }}
private-key: ${{ secrets.CI_APP_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
fetch-depth: 0
persist-credentials: true
# Setup Python - semantic-release might need it or specific tools
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
# Configured with PyPI Trusted Publishing (OIDC)
# https://docs.pypi.org/trusted-publishers/
- name: Python Semantic Release
id: semantic-release
uses: python-semantic-release/python-semantic-release@v9
with:
github_token: ${{ steps.generate-token.outputs.token }}
- name: Publish package to PyPI
if: steps.semantic-release.outputs.released == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
print-hash: true
# Check if Dockerfile exists
- name: Check for Dockerfile
id: check_dockerfile
run: |
if [ -f "Dockerfile" ]; then
echo "dockerfile_exists=true" >> $GITHUB_OUTPUT
else
echo "dockerfile_exists=false" >> $GITHUB_OUTPUT
fi
# Set up QEMU for multi-platform builds
- name: Set up QEMU
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
uses: docker/setup-qemu-action@v3
# Set up Docker Buildx
- name: Set up Docker Buildx
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
uses: docker/setup-buildx-action@v3
# Define image name variables for reusability
- name: Set image name variables
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
id: image-names
run: |
# Define the GitHub Container Registry image name (using organization)
GITHUB_IMG="${{ vars.GCR_IMAGE_NAME }}"
# Define the Docker Hub image name (using personal account)
DOCKERHUB_IMG="${{ vars.DOCKERHUB_IMAGE_NAME }}"
# Convert to lowercase (Docker best practice)
GITHUB_IMG=$(echo "$GITHUB_IMG" | tr '[:upper:]' '[:lower:]')
DOCKERHUB_IMG=$(echo "$DOCKERHUB_IMG" | tr '[:upper:]' '[:lower:]')
echo "GCR_IMAGE_NAME=$GITHUB_IMG" >> $GITHUB_ENV
echo "DOCKERHUB_IMAGE_NAME=$DOCKERHUB_IMG" >> $GITHUB_ENV
# Log in to Docker Hub
- name: Login to Docker Hub
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Extract version for tagging (only if Dockerfile exists)
- name: Get project version
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
id: get-version
run: |
# If semantic release created a new version, use it; otherwise extract from pyproject.toml
if [ "${{ steps.semantic-release.outputs.released }}" == "true" ]; then
VERSION="${{ steps.semantic-release.outputs.version }}"
else
VERSION=$(grep -m 1 'version = ' pyproject.toml | sed 's/version = //; s/"//g; s/^[[:space:]]*//; s/[[:space:]]*$//')
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "version_tag=v$VERSION" >> $GITHUB_OUTPUT
# Log in to GitHub Container Registry
- name: Login to GitHub Container Registry
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# Build and push Docker image
- name: Build and push Docker image
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ env.DOCKERHUB_IMAGE_NAME }}:latest
${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.get-version.outputs.version }}
ghcr.io/${{ env.GCR_IMAGE_NAME }}:latest
ghcr.io/${{ env.GCR_IMAGE_NAME }}:${{ steps.get-version.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.title=NyaProxy
org.opencontainers.image.description=A lightweight, flexible API proxy with dynamic token rotation
org.opencontainers.image.url=https://github.com/Nya-Foundation/nyaproxy
org.opencontainers.image.source=https://github.com/Nya-Foundation/nyaproxy
org.opencontainers.image.version=${{ steps.get-version.outputs.version }}
org.opencontainers.image.created=${{ github.event.repository.updated_at }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Scan Docker image
if: steps.check_dockerfile.outputs.dockerfile_exists == 'true'
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.get-version.outputs.version }}
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
# .github/workflows/publish.yml # # Standard Publish Workflow for Nya Foundation # # This workflow handles the full CI/CD pipeline for Foundation: # 1. Runs tests and checks # 2. Uses semantic-release to automatically version the project based on commit messages # 3. Publishes the package to PyPI # 4. Builds and pushes Docker images to both Docker Hub and GitHub Container Registry # 5. Creates GitHub releases with changelog # # Required Secrets: # - CI_APP_ID: GitHub App ID for token generation # - CI_APP_PRIVATE_KEY: GitHub App private key for token generation # - DOCKERHUB_USERNAME: Username for Docker Hub # - DOCKERHUB_TOKEN: Access token for Docker Hub (not password) # # Optional Variables (set in repository variables): # - GCR_IMAGE_NAME: Image name for GitHub Container Registry (example: nya-foundation/nya-proxy) # - DOCKERHUB_IMAGE_NAME: Image name for Docker Hub (example: k3scat/nya-proxy) # # Permissions needed: # - contents: write (for creating GitHub releases) # - id-token: write (for PyPI trusted publishing) # - packages: write (for publishing to GitHub Container Registry) name: CI - Publish and Release on: push: branches: - main # Trigger release only on pushes to main paths: - '**/*.py' # Trigger on changes to Python files # Prevent multiple concurrent releases concurrency: group: ${{ github.workflow }}-release cancel-in-progress: false # Do not cancel releases once started jobs: publish: timeout-minutes: 30 runs-on: latchkey-small environment: name: Publish and Release permissions: contents: write # Needed to create GitHub releases and tags id-token: write # Needed for PyPI trusted publishing (OIDC) - Recommended! packages: write # Needed for publishing Docker images steps: # Generate Token for the GitHub App - name: Generate GitHub App Token id: generate-token # Uses App ID and Private Key secrets to generate a short-lived installation token uses: actions/create-github-app-token@v2 with: app-id: ${{ secrets.CI_APP_ID }} private-key: ${{ secrets.CI_APP_PRIVATE_KEY }} - name: Checkout code uses: actions/checkout@v4 with: token: ${{ steps.generate-token.outputs.token }} fetch-depth: 0 persist-credentials: true # Setup Python - semantic-release might need it or specific tools - name: Set up Python uses: actions/setup-python@v5 with: cache: 'pip' python-version: '3.12' # Configured with PyPI Trusted Publishing (OIDC) # https://docs.pypi.org/trusted-publishers/ - name: Python Semantic Release id: semantic-release uses: python-semantic-release/python-semantic-release@v9 with: github_token: ${{ steps.generate-token.outputs.token }} - name: Publish package to PyPI if: steps.semantic-release.outputs.released == 'true' uses: pypa/gh-action-pypi-publish@release/v1 with: verbose: true print-hash: true # Check if Dockerfile exists - name: Check for Dockerfile id: check_dockerfile run: | if [ -f "Dockerfile" ]; then echo "dockerfile_exists=true" >> $GITHUB_OUTPUT else echo "dockerfile_exists=false" >> $GITHUB_OUTPUT fi # Set up QEMU for multi-platform builds - name: Set up QEMU if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' uses: docker/setup-qemu-action@v3 # Set up Docker Buildx - name: Set up Docker Buildx if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' uses: docker/setup-buildx-action@v3 # Define image name variables for reusability - name: Set image name variables if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' id: image-names run: | # Define the GitHub Container Registry image name (using organization) GITHUB_IMG="${{ vars.GCR_IMAGE_NAME }}" # Define the Docker Hub image name (using personal account) DOCKERHUB_IMG="${{ vars.DOCKERHUB_IMAGE_NAME }}" # Convert to lowercase (Docker best practice) GITHUB_IMG=$(echo "$GITHUB_IMG" | tr '[:upper:]' '[:lower:]') DOCKERHUB_IMG=$(echo "$DOCKERHUB_IMG" | tr '[:upper:]' '[:lower:]') echo "GCR_IMAGE_NAME=$GITHUB_IMG" >> $GITHUB_ENV echo "DOCKERHUB_IMAGE_NAME=$DOCKERHUB_IMG" >> $GITHUB_ENV # Log in to Docker Hub - name: Login to Docker Hub if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # Extract version for tagging (only if Dockerfile exists) - name: Get project version if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' id: get-version run: | # If semantic release created a new version, use it; otherwise extract from pyproject.toml if [ "${{ steps.semantic-release.outputs.released }}" == "true" ]; then VERSION="${{ steps.semantic-release.outputs.version }}" else VERSION=$(grep -m 1 'version = ' pyproject.toml | sed 's/version = //; s/"//g; s/^[[:space:]]*//; s/[[:space:]]*$//') fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version_tag=v$VERSION" >> $GITHUB_OUTPUT # Log in to GitHub Container Registry - name: Login to GitHub Container Registry if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} # Build and push Docker image - name: Build and push Docker image if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' uses: docker/build-push-action@v5 with: context: . push: true platforms: linux/amd64,linux/arm64 tags: | ${{ env.DOCKERHUB_IMAGE_NAME }}:latest ${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.get-version.outputs.version }} ghcr.io/${{ env.GCR_IMAGE_NAME }}:latest ghcr.io/${{ env.GCR_IMAGE_NAME }}:${{ steps.get-version.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max labels: | org.opencontainers.image.title=NyaProxy org.opencontainers.image.description=A lightweight, flexible API proxy with dynamic token rotation org.opencontainers.image.url=https://github.com/Nya-Foundation/nyaproxy org.opencontainers.image.source=https://github.com/Nya-Foundation/nyaproxy org.opencontainers.image.version=${{ steps.get-version.outputs.version }} org.opencontainers.image.created=${{ github.event.repository.updated_at }} org.opencontainers.image.revision=${{ github.sha }} - name: Scan Docker image if: steps.check_dockerfile.outputs.dockerfile_exists == 'true' uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.get-version.outputs.version }} format: 'table' exit-code: '1' ignore-unfixed: true vuln-type: 'os,library' severity: 'CRITICAL,HIGH'
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
7 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Container pulls and builds
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.