Instruction file imported from deors/.github (
.github/instructions/github-actions.instructions.md). Copyright stays with the author.
Workflow Structure and Organization
- Use clear, descriptive workflow and job names
- Organize workflows by purpose (CI, CD, release, security, etc.) in separate files
- Always set
permissionsat the workflow or job level using least-privilege; default tocontents: read
Workflow Triggers
- Choose appropriate triggers (
push,pull_request,schedule,workflow_dispatch, etc.) - Use
pathsfilters so workflows run only when relevant files change - Use
branchesfilters to target specific branches; combine withpaths-ignorewhere needed - Use
workflow_dispatchwith typedinputsfor manually triggered workflows
Job Configuration
- Use descriptive job IDs and
namefields - Set
timeout-minuteson all jobs to prevent hanging runs - Use
needsto declare job dependencies; useifconditionals to skip jobs when not needed - Set
concurrencywithcancel-in-progress: trueto avoid redundant runs on the same branch
Steps Best Practices
- Pin action versions to a full-length commit SHA for production workflows, or to a semantic version tag (e.g.,
@v7) for lower-risk use cases - Use official GitHub-maintained actions (
actions/*) before third-party alternatives - Name every step clearly; use
ifconditionals to control optional step execution
Security
- Store all secrets in GitHub Secrets and reference them with
${{ secrets.SECRET_NAME }}; never hardcode credentials - Use GitHub OIDC (
id-token: write) for authenticating to cloud providers instead of long-lived static credentials - Set minimal
permissions— explicitly grant only what each job needs - Use
github/codeql-actionfor automated security scanning on push and pull request events - Use environments with protection rules (required reviewers, deployment branches) for production deployments
- Pin third-party actions to a commit SHA and review them before use
Caching and Optimization
- Cache dependencies using tool-specific built-in caching (
setup-java cache: maven,setup-node cache: npm) before falling back toactions/cache@v6 - For Java workflows, always use the latest LTS version; verify at https://adoptium.net/temurin/releases/ and update
java-versionaccordingly rather than hardcoding an older release - Use
actions/upload-artifact@v7andactions/download-artifact@v8to share data between jobs - Run independent jobs in parallel; use
needsonly when there is a true dependency - Use
fetch-depth: 1(shallow clone) unless full history is required - Use
concurrencygroups to cancel superseded runs on feature branches
Matrix Builds
- Use
strategy.matrixto test across multiple versions or platforms - Set
fail-fast: falsewhen you want all matrix combinations to complete even if one fails - Use
include/excludeto add or remove specific combinations - Keep matrix dimensions small to avoid excessive job counts
Reusable Workflows and Composite Actions
- Extract repeated patterns into reusable workflows (
workflow_call) or composite actions - Document all
inputs,outputs, andsecretsin reusable workflows with descriptions and types - Version composite actions with tags; reference them by tag or commit SHA
Environment Variables and Secrets
- Prefer
vars.*(repository/environment variables) for non-sensitive configuration andsecrets.*for sensitive values - Set
envat the lowest applicable scope (step > job > workflow) - Document required secrets and variables in the workflow file comments or the repository README
Common Action Versions
Check each action's releases page for the current major version before pinning; the list below reflects the latest majors as of mid-2026.
actions/checkout@v7actions/setup-node@v6actions/setup-java@v5actions/setup-python@v6actions/cache@v6actions/upload-artifact@v7actions/download-artifact@v8github/codeql-action/init@v4github/codeql-action/analyze@v4codecov/codecov-action@v7docker/build-push-action@v7docker/login-action@v4aws-actions/configure-aws-credentials@v6dorny/test-reporter@v3
Example: CI Workflow (Maven, compile/test + package/publish)
This is the standard shape for Java/Maven projects: a compile-and-unit-test job that runs tests, coverage and dependency scanning, followed by a package-and-publish job that packages and publishes artifacts to GitHub Packages, reusing the compiled output via cache.
name: Continuous Integration
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
compile-and-unit-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JDK 25
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'temurin'
- name: Set up Maven dependency cache
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ github.sha }}
restore-keys: ${{ runner.os }}-m2-
- name: Run Maven compile and unit test
run: mvn test
- name: Run JaCoCo code coverage report
run: mvn org.jacoco:jacoco-maven-plugin:report
- name: Generate unit test report
uses: dorny/test-reporter@v3
if: success() || failure()
with:
name: unit-test-report
path: target/surefire-reports/*.xml
reporter: java-junit
fail-on-error: true
- name: Run dependency scan
run: mvn dependency-check:check -Dnvd.api.key=${{ secrets.NVD_API_KEY }}
- name: Cache compiled classes
uses: actions/cache@v6
with:
path: |
target/classes
target/test-classes
target/surefire-reports
key: ${{ runner.os }}-compiled-${{ github.sha }}
restore-keys: ${{ runner.os }}-compiled-${{ github.sha }}
package-and-publish:
runs-on: ubuntu-latest
needs: compile-and-unit-test
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JDK 25
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'temurin'
- name: Set up Maven dependency cache
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ github.sha }}-deploy
restore-keys: ${{ runner.os }}-m2-
- name: Restore compiled classes cache
uses: actions/cache@v6
with:
path: |
target/classes
target/test-classes
target/surefire-reports
key: ${{ runner.os }}-compiled-${{ github.sha }}
restore-keys: ${{ runner.os }}-compiled-${{ github.sha }}
- name: Package artifacts
run: mvn package -DskipTests
- name: Install artifacts to cached Maven repository
run: mvn jar:jar install:install
- name: Publish artifacts to GitHub Packages
run: mvn jar:jar deploy:deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Key points of this model:
- Split verification (
compile-and-unit-test) from packaging/publishing (package-and-publish) usingneedsso packaging only runs after tests, coverage and the dependency scan pass - Cache the Maven local repository (
~/.m2) keyed bygithub.sha, plus a separate cache for compiled classes so thepackage-and-publishjob doesn't recompile or rerun tests - Use
dorny/test-reporterto publish JUnit results as a check run, runningif: success() || failure()so results are reported even when tests fail - Run OWASP dependency-check (
mvn dependency-check:check) with the NVD API key from secrets as part of the verification job - Restrict
package-and-publishpermissions tocontents: readandpackages: write(least privilege for publishing to GitHub Packages)
Debugging Tips
- Enable step debug logging by setting the
ACTIONS_STEP_DEBUGsecret totrue - Use
actions/upload-artifact@v7to preserve logs and test results for failed runs - Output variable values with
echo "value=${{ env.MY_VAR }}"in run steps - Test workflows locally with the
actCLI when possible