{"id":26372137,"url":"https://github.com/vojtechmares/github-actions-training","last_synced_at":"2026-01-03T10:34:47.521Z","repository":{"id":282217810,"uuid":"892849077","full_name":"vojtechmares/github-actions-training","owner":"vojtechmares","description":"GitHub Actions course","archived":false,"fork":false,"pushed_at":"2025-03-13T11:06:09.000Z","size":11,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-13T12:22:37.960Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vojtechmares.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-22T22:36:19.000Z","updated_at":"2025-03-13T11:06:12.000Z","dependencies_parsed_at":"2025-03-13T12:22:40.025Z","dependency_job_id":"25147e6c-ca59-46d5-b8b7-73a8cd47aad6","html_url":"https://github.com/vojtechmares/github-actions-training","commit_stats":null,"previous_names":["vojtechmares/github-actions-training"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fgithub-actions-training","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fgithub-actions-training/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fgithub-actions-training/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fgithub-actions-training/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vojtechmares","download_url":"https://codeload.github.com/vojtechmares/github-actions-training/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243955682,"owners_count":20374373,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-17T00:47:53.486Z","updated_at":"2026-01-03T10:34:47.471Z","avatar_url":"https://github.com/vojtechmares.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable MD033 --\u003e\n\u003c!-- markdownlint-disable-next-line MD041 --\u003e\n\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\" style=\"font-size: 4rem;\"\u003eGitHub Actions Training\u003c/h1\u003e\n  \u003cp align=\"center\" style=\"font-size: 1.5rem;\"\u003e\n    Automate your tests, builds, releases, and more on every push!\n  \u003c/p\u003e\n  \u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/features/actions\"\u003e\u003cimg alt=\"GitHub Actions\" src=\"https://img.shields.io/badge/TRAINING ON-GITHUB ACTIONS-2088FF?style=for-the-badge\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.mares.cz\"\u003e\u003cimg alt=\"Vojtěch Mareš\" src=\"https://img.shields.io/badge/TRAINING BY-Vojtěch Mareš-fd9a00?style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n  \u003cp align=\"center\"\u003e\u003ca href=\"https://www.mares.cz\"\u003eVojtěch Mareš\u003c/a\u003e | \u003ca href=\"mailto:vojtech@mares.cz\"\u003evojtech@mares.cz\u003c/a\u003e\u003c/p\u003e\n\u003c/p\u003e\n\u003c!-- markdownlint-enable MD033 --\u003e\n\n## What is Continuous Integration (CI)\n\n### Git\n\nGit is the most popular Version Control System (VCS) in the world, created by Linus Torvalds for the Linux Kernel development, since the tools available at the time were not up to the task according to Linus.\n\n### GitHub\n\nGitHub is Git repository hosting platform and today's de-factor home of (almost) all open-source software.\n\n### Continuous Integration (CI)\n\nContinuous Integration is a DevOps process, of always testing every version of code pushed to the repository by running tests, building the application, etc.\n\n### Use case for CI\n\n- automated tests\n- automated builds\n- (almost) immediate feedback on fail\n- automated Issue and PR management (labels, auto-close,...)\n\n## GitHub Actions\n\n### GitHub Actions features\n\n- Built around reusability\n- Easy customization\n- Use of existing components\n- Supported by GitHub.com (SaaS and Enterprise Cloud)\n- Supported by self-hosted GitHub (GitHub Enterprise Server)\n\n### GitHub Actions architecture\n\nEvent (Trigger) -\u003e Job(s) -\u003e Runner -\u003e Execute Job\n\n### Runner(s)\n\n- **Managed**: GitHub.com managed runners\n  - linux runners (Ubuntu)\n  - macOS runners (paid only)\n  - Windows runners (paid only)\n  - Limited sizing options\n- **Self-hosted**\n  - systemd unit on Linux\n  - [Actions Runner Controller (ARC)](https://github.com/actions/actions-runner-controller) for Kubernetes\n  - third-party providers of managed runners\n\n## Actions\n\nAction is a reusable component. You can use pre-existing actions, see [GitHub Marketplace](https://github.com/marketplace?type=actions) or build your own.\n\nIn each step of a workflow, only a single action or composable action can be used.\n\n## Using existing actions\n\nTo use an existing action, just reference it in your workflow with `uses` directive.\n\nFor example:\n\n```yaml\n# uses \u003corg\u003e/\u003crepo\u003e@\u003cref\u003e\nuses: actions/checkout@v4\n```\n\n## Workflows\n\nEach workflow resides in a single file inside the `.github/workflows/` directory.\n\nA workflow has the following required parameters:\n\n- **name**: name of the workflow visible in the UI\n- **trigger** (`on`): what triggers and what does not trigger the workflow\n- **jobs**: a list of steps executed in order\n\nThe workflow file has well defined structure:\n\n```yaml\n# Workflow name visible in 'Actions' tab on repository page\nname: xxx\n\n# Triggers\non:\n  push:\n    branches:\n      - '*'\n\n# Jobs\njobs:\n  first:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: xxx\n        run: |\n          echo \"I am the first step!\"\n\n  second:\n    needs: [ \"first\" ]\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: yyy\n        run: |\n          echo \"I am first step of second job\"\n```\n\nEach job is composed of multiple steps. A job can have if condition to further control its execution.\n\nEach step can also have its if condition.\n\n## Visual Studio Code extensions\n\n- [GitHub Actions](https://marketplace.visualstudio.com/items?itemName=github.vscode-github-actions) by GitHub: Provides intellisense and sidebar plugin to see Workflow runs\n- [YAML](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) by RedHat: correct YAML syntax highlighting\n\nOr see and use [`.vscode/extensions.json`](/.vscode/extensions.json).\n\n## Writing workflows\n\n### Simple workflow\n\n```yaml\nname: Simple workflow\n\non: [push]\n\njobs:\n  hello-world:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Say hello\n        run: echo \"Hello, world!\"\n```\n\n### Jobs\n\nEach job is executed on a single runner. If your job is running in GitHub managed runners, each runner runs exactly one job, and the runner virtual machine is removed after the job is finished.\n\n### Steps\n\nStep can either execute a shell script in given shell, which you must specify. Pr use an existing action and supply its inputs, if it has any.\n\n```yaml\njobs:\n  my-job:\n    # ...\n    steps:\n      - name: First step # name is optional\n        shell: bash # bash, sh, pwsh, python, cmd\n        run: |\n          echo \"Hello everyone from bash\"\n\n      - name: Second step with powershell\n        shell: pwsh\n        run: |\n          Write-Output 'Hello everyone from pwsh'\n```\n\nIf you are using action, do not forget to tag the action version via `@` notation (e.g. `docker/login-action@v2`). You can use any version tag, branch name or lock it down to a commit hash, but that is difficult to read by humans and identify the version used.\n\n### Clone repository with step that uses actions/checkout\n\nBy default, the repository is never cloned/checkout to the workspace of the runner. That must be done explicitly via a step that uses [actions/checkout](https://github.com/actions/checkout) action.\n\n```yaml\nsteps:\n  - name: Checkout\n    uses: actions/checkout@v4\n```\n\n### Triggers\n\nWhen a workflow is executed.\n\nCommon cases are:\n\n- **Push**\n\n    ```yaml\n    name: On push\n\n    on:\n      push:\n        branches:\n          - main\n        ignore-branches:\n          - '*'\n        # tags:\n        # ignore-tags:\n    ```\n\n- **Pull request**\n\n    ```yaml\n    name: On pull request\n\n    on:\n      pull_request:\n        branches:\n          - main # only on PRs that target 'main' branch\n    ```\n\n- **Schedule** (cron)\n\n    ```yaml\n    name: On schedule\n\n    on:\n      schedule:\n        # * is a special character in YAML so you have to quote this string\n        - cron: '30 5,17 * * *'\n        # Multiple cron expressions can be supplied\n        - cron: '30 5 * * 1,3'\n        - cron: '30 5 * * 2,4'\n    ```\n\n- **Issue**\n\n    ```yaml\n    name: On issue\n\n    on:\n      issues:\n        types: [opened, closed, edited, milestoned]\n    ```\n\n    For list of types, see [docs](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#issues).\n\n- **Tag (release)**\n\n    ```yaml\n    name: On push of tag\n\n    on:\n      push:\n        tags:\n          - 'v*'\n    ```\n\n- **Previous workflow**\n\n    ```yaml\n    name: After previous workflow finishes\n\n    on:\n      workflow_run:\n        workflows: [ \"Test\" ]\n        types: [ \"completed\" ]\n\n    jobs:\n      build:\n        runs-on: ubuntu-latest\n        # without this if, workflow will run regardless if the previous workflow succeeded or not\n        if: github.event.workflow_run.conclusion == 'success'\n        # ...\n    ```\n\n- **Changes in path**\n\n    ```yaml\n    name: On path changes\n\n    on:\n      push:\n        paths:\n          - src\n          - tests\n    ```\n\nGitHub docs:\n\n- [Triggering a workflow](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow)\n- [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows) contains a list of all triggers\n\n### Needs\n\nWhen a job _needs_ some other job within the workflow (pipeline) to run before, use keyword `needs` and list of names of the depending jobs.\n\n```yaml\non: Needs example\n\njobs:\n  first:\n    runs-on: ubuntu-latest\n\n    steps:\n      - run: echo \"Hello world\"\n\n  second:\n    runs-on: ubuntu-latest\n\n    needs: [first]\n\n    steps:\n      - run: echo \"Hello world from second job\"\n```\n\n### Contexts\n\nContexts are groups of related variables, that you can use in your workflows to write ifs, reference environment variables, secrets, environments, or get metadata from runner.\n\nUsually used contexts are:\n\n- **github** - references to Git ref, triggering event, etc.\n- **env** - environment variables, can also be referenced with classic shell notation `$ENV_VAR`\n- **secrets** - only way to access secrets\n\nFor more information, see [docs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs).\n\n**Using contexts**:\n\nContexts and other GitHub Actions built-in function are accessible inside the `${{ env.MY_VAR }}` brackets. That way it is clearly distinguishable from regular shell variables. Also with GitHub Actions Visual Studio Code extension, you get syntax highlighting and intellisense. For more examples, see [Conditionals](#composite-actions).\n\n### Builtin functions\n\nGitHub Actions have several builtin functions, that can be used inside _expressions_. Expression is `${{ \u003cexpression\u003e }}` block. An expression can call a function, use logical operator to return a boolean value or filter objects.\n\n**Functions**:\n\n- `contains( search, item )`: returns `true` if `search` contains `item`\n- `startsWith( searchString, searchValue )`: returns `true` if `searchString` starts with `searchValue`, aka prefix\n- `endsWith( searchString, searchValue )`: returns `true` if `searchString` ends with `searchValue`, aka suffix\n- `format( string, replaceValue0, replaceValue1, ..., replaceValueN)`: replaces values in the `string`, with the variable `replaceValueN`\n- `join( array, optionalSeparator )`: The value for array can be an array or a string. All values in array are concatenated into a string, default separator is `,`\n- `toJSON(value)`: returns a pretty-print JSON representation of `value`, useful for debugging of [Contexts](#contexts)\n- `fromJSON(value)`: returns a JSON object or JSON data type for value\n- `hashFiles(path)`: returns a single hash for the set of files that matches the `path` pattern, you can provide a single path pattern or multiple path patterns separated by commas\n\n**Logical operators**:\n\n- `( )`: logical group\n- `[ ]`: index (array access)\n- `.`: property de-reference\n- `!`: not\n- `\u003c`: less than\n- `\u003e`: more than\n- `==`: equal\n- `!=`: not equal\n- `\u003e=`: more or equal\n- `\u003c=`: less or equal\n- `\u0026\u0026`: and\n- `||`: or\n\n**Filtering objects**:\n\nExample object:\n\n```json\n[\n  { \"name\": \"apple\", \"quantity\": 1 },\n  { \"name\": \"orange\", \"quantity\": 2 },\n  { \"name\": \"pear\", \"quantity\": 1 }\n]\n```\n\nFilter:\n\n```text\nfruits.*.name\n```\n\nOutput:\n\n```json\n[ \"apple\", \"orange\", \"pear\" ]\n```\n\nFor more information, see [docs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions).\n\n### Conditionals\n\nEach job and/or step can contain an `if` statement, to further control execution.\n\nFor example, if you are reusing generic workflows for `main` branch and pull requests, you may not want to deploy to from a pull request.\n\n**Job with `if` example**:\n\n```yaml\nname: if repository\n\non: [push]\n\njobs:\n  production-deploy:\n    # run job only in main repository, not on forks, since forks have different org and/or repo name\n    if: github.repository == 'octo-org/octo-repo-prod'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '14'\n      - run: npm install -g bats\n```\n\n**Step with `if` example**:\n\n```yaml\nsteps:\n  - name: My first step\n    if: ${{ github.event_name == 'pull_request' \u0026\u0026 github.event.action == 'unassigned' }}\n    run: echo This event is a pull request that had an assignee removed.\n```\n\n### Defaults\n\nDefaults are workflow wide configurations, so you do not have to repeat the configuration per job or per step.\n\nThe default options are:\n\n- `defaults.run.shell`\n- `defaults.run.working-directory`\n\nDefaults can be defined at workflow or at job level.\n\nJob defaults:\n\n```yaml\njob:\n  job-a:\n    defaults:\n      run:\n        shell: pwsh\n        working-directory: win\n    # ...\n```\n\n### Working directory\n\nBy default the working directory is the repository root directory, in some cases you may want to change this behavior.\n\nSuch change works for the shell steps.\n\n\u003e [!IMPORTANT]\n\u003e You must make sure, that the directory exists.\n\n```yaml\n# defaults for entire workflow\ndefaults:\n  run:\n    working-directory: /some/path\n\n# per job\njobs:\n  job-alice:\n    runs-on: ubuntu-latest\n    working-directory: /some/path\n\n# per step\njobs:\n  job-bob:\n    runs-on: ubuntu-latest\n\n    steps:\n      - working-directory: /some/path\n        shell: bash\n        run: ls\n```\n\n### Concurrency\n\nConcurrency allows you to control, if a certain workflow or job should run in parallel and even between different workflow runs.\n\nFor example you may not want to deploy an older version of an application if a new version was created in the meantime and is suppose to be deployed to an environment.\n\nDefault behavior:\n\n```yaml\non:\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n```\n\nConcurrency groups:\n\n```yaml\njobs:\n  job-1:\n    runs-on: ubuntu-latest\n    ## job-level concurrency\n    concurrency:\n      group: staging_environment\n      cancel-in-progress: true\n\n# OR\n\n## workflow concurrency (for all jobs in workflow)\nconcurrency:\n  group: ci-${{ github.ref }}\n  cancel-in-progress: true\n```\n\nFallback value:\n\n```yaml\nconcurrency:\n  group: ${{ github.head_ref || github.run_id }}\n  cancel-in-progress: true\n```\n\nOnly cancel in-progress on specific branches:\n\n```yaml\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ !contains(github.ref, 'release/')}}\n```\n\n### Environment variables\n\nEnvironment variables for Organizations are located at: _[Organization] Settings -\u003e Secrets and variables (left sidebar) -\u003e Actions (dropdown menu)_.\n\nEnvironment variables for Repositories are located at: _[Repository] Settings \u003e Secrets and variables (left sidebar) \u003e Actions (dropdown)_.\n\nEnvironment variables are for non-secret values only, they can be defined at many levels:\n\n- **Organization**: environment variable available for all repositories and workflows within the organization\n- **Repository**: environment variables defined in settings of a repository\n- **Repository Environment**: each repository can have environments for deployments and environment variables can be environment-specific and available only to jobs/steps that are deploying to the environment\n- **Workflow**: environment variables defined under `.defaults.env` available for all jobs in a workflow\n- **Job**: environment variable defined per job `.jobs.\u003cjob_id\u003e.env`\n- **Step**: environment variable defined per step `.jobs.\u003cjob_id\u003e.steps[*].env`\n- **Dynamic**: environment variables can be declared dynamically in a step and the be available for all consequential steps in the job\n\n    ```yaml\n    name: Dynamic environment variable\n\n    on:\n      push:\n\n    jobs:\n      how-to-declared-env-var:\n        runs-on: ubuntu-latest\n\n        steps:\n          - name: Declare env var\n            run: |\n              echo \"HELLO=world\" \u003e\u003e $GITHUB_ENV\n\n          - name: Use env var\n            run: |\n              echo $HELLO # prints \"world\"\n    ```\n\n**Overrides**:\n\nSecrets have a certain hierarchy when overriding with multiple configuration sources, this hierarchy goes as follows: _Dynamic environment variable \u003e Step environment variable \u003e Job environment variable \u003e Workflow environment variable \u003e Repository Environment environment variable \u003e Repository environment variable \u003e Organization environment variable_.\n\n### Secrets\n\nSecrets are special values which are securely stored in GitHub and redacted from logs. Making it harder to leak them.\n\nSecrets for Organizations are located at: _[Organization] Settings -\u003e Secrets and variables (left sidebar) -\u003e Actions (dropdown menu)_.\n\nSecrets for Repositories are located at: _[Repository] Settings \u003e Secrets and variables (left sidebar) \u003e Actions (dropdown)_.\n\nThere are few options, where a secret can be defined:\n\n- **Organization**: each GitHub organization can have its secrets, which are available to all repositories under the organization\n- **Repository**: secrets available only for a single repository (do not forget that secrets can be passed to workflows even when workflow resides in different repository)\n- **Repository Environment**: each repository can have environments for deployments and secrets can be environment-specific and available only to jobs/steps that are deploying to the environment\n\n**Overrides**:\n\nSecrets have a certain hierarchy when overriding with multiple configuration sources, this hierarchy goes as follows: _Repository Environment secret \u003e Repository secret \u003e Organization secret_.\n\n### GitHub token (`secrets.GITHUB_TOKEN`)\n\nThe value of `secrets.GITHUB_TOKEN` is a token generated by GitHub for every job. The token is short lived for the duration of a single job. After the job is finished, the token is invalidated.\n\n### Permissions\n\nPermissions are list of privileges that `secretes.GITHUB_TOKEN` has attached to itself.\n\n```yaml\npermissions:\n  actions: read|write|none\n  attestations: read|write|none\n  checks: read|write|none\n  contents: read|write|none\n  deployments: read|write|none\n  id-token: write|none\n  issues: read|write|none\n  discussions: read|write|none\n  packages: read|write|none\n  pages: read|write|none\n  pull-requests: read|write|none\n  repository-projects: read|write|none\n  security-events: read|write|none\n  statuses: read|write|none\n```\n\nA short explanation and use case for each permission:\n\n- **actions**: interact with GitHub Actions, `actions: write` can cancel action\n- **attestations**: working with artifact attestations, `attestations: write` let's you create attestation for artifact for a build\n- **checks**: work with check runs and check suites, `check: write` permits an action to create a check run\n- **content**: interact with repository, commits, etc., `content: read` permits action to list commits, `content: write` permits action to create a release\n- **deployments**: work with deployments, `deployments: write` permits action to create a deployment\n- **id-token**: fetch an OpenID Connect (OIDC) token, requires `id-token: write`\n- **issues**: work with issues, `issues: write` let's action to write a comment for example\n- **discussions**: work with discussions, `discussions: write` permits action to close a discussion\n- **packages**: work with packages, `packages: write` permits action to upload and publish a package\n- **pages**: work with pages, `pages: write` permits action to trigger GitHub Pages build\n- **pull-requests**: work with pull requests, `pull-request: write` permits action to comment, open, close or add label\n- **repository-projects**: work with repository projects (classic), `repository-projects: write` permits action to add a column to a repository project (classic)\n- **security-events**: work with GitHub code scanning and Dependabot alerts, `security-events: read` permits action to list alerts, `security-events: write` allows the action to update the status of code scanning alert\n- **statuses**: work with commits statuses, `statuses: read` permits an action to list the commit statuses for a given reference\n\nA common use is for **packages** to push Docker image to GitHub container registry. **Issues** and **pull requests** for automated labeling.\n\nFor more information, see [docs](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#permissions).\n\n### Continue on error\n\nIn some cases you want a step/job as an optional, that the failure of the step/job does not fail the job/workflow as a whole.\n\n```yaml\nsteps:\n  - name: First step\n    run: echo \"Hello world\"\n\n  - name: I am allowed to fail\n    continue-on-error: true\n    run: echo \"Hello world\" \u0026\u0026 false\n```\n\nContinue on error behavior can (and often is) adopted when having a company (GitHub Organization) policy. When a new policy is introduced, the compliance job running on every single repository has the new check will have a grace period where the job/workflow has `continue-on-error` set to `true`, so everyone can see if they are compliant yet or not, but it does not block anyone's work. After the grace period is over, everyone should be compliant with the new policy and should not cause issues.\n\n### Machine on which job is running\n\nTo select a machine to run the job on, use the `runs-on`.\n\nThe `runs-on` directive is also used to select self-hosted runners instead of the GitHub managed (default).\n\n`runs-on` can also be a list, the workflow will be executed by a runner that matches all of the criteria.\n\n```yaml\nruns-on: [self-hosted, linux, x64, gpu]\n```\n\nWhen running on GitHub managed runners, most often jobs are using `runs-on: ubuntu-latest`, but if you want increased stability and greater control, consider using `runs-on: ubuntu-24.04` (or any Ubuntu LTS version supported by GitHub Runners).\n\n### Strategy\n\n\u003e Use `jobs.\u003cjob_id\u003e.strategy` to use a matrix strategy for your jobs. A matrix strategy lets you use variables in a single job definition to automatically create multiple job runs that are based on the combinations of the variables. For example, you can use a matrix strategy to test your code in multiple versions of a language or on multiple operating systems.\n\nA common use-case for strategy matrix is testing your program on multiple runtime versions, different platforms (macOS, Windows, Linux), and CPU architecture (x86_64, aarch64,...)\n\nMultiple matrix (multi-dimensional):\n\n```yaml\njobs:\n  example_matrix:\n    strategy:\n      matrix: # this matrix will generate 9 jobs\n        version: [10, 12, 14]\n        os: [ubuntu-latest, windows-latest]\n```\n\nSingle dimensional:\n\n_This is commonly used for testing on multiple versions of runtime, like Node.js or dotnet._\n\n```yaml\njobs:\n  example_matrix:\n    strategy:\n      matrix: # this matrix generates 4 jobs\n        version: [18, 20, 22, 23] # last 3 LTS releases and latest stable\n    steps:\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.version }}\n```\n\n### Services\n\n\u003e Used to host service containers for a job in a workflow. Service containers are useful for creating databases or cache services like Redis. The runner automatically creates a Docker network and manages the life cycle of the service containers.\n\n```yaml\nservices:\n  nginx:\n    image: nginx\n    # Map port 8080 on the Docker host to port 80 on the nginx container\n    credentials:\n      username: ${{ secrets.SERVICES_USERNAME }} # must be provided manually in Secrets\n      password: ${{ secrets.SERVICES_PASSWORD }} # must be provided manually in Secrets\n    ports:\n      - 8080:80\n  redis:\n    image: redis\n    # Map random free TCP port on Docker host to port 6379 on redis container\n    ports:\n      - 6379/tcp\nsteps:\n  - run: |\n      echo \"Redis available on 127.0.0.1:${{ job.services.redis.ports['6379'] }}\"\n      echo \"Nginx available on 127.0.0.1:${{ job.services.nginx.ports['80'] }}\"\n```\n\n## Environments\n\nGitHub offers environments. Environments support environment-specific environment variables and secrets (mentioned before at [Environment variables](#environment-variables) and [Secrets](#secrets)).\n\nEnvironment also provides a UI element on repository's homepage (right sidebar, _Deployments_. _Deployments_ belong to environments).\n\nEnvironments are often integrated with third-party services such as Vercel or Cloudflare Pages, to show a deployment status.\n\n## Reusable workflows\n\nYou do not have to repeat yourself for every case, you can build reusable workflows, that can be trigger as jobs in other workflows.\n\nExample:\n\n```yaml\n# .github/workflows/reusable-build.yml\nname: Reusable build\n\non:\n  workflow_call:\n    inputs:\n      some-value:\n        required: true\n        type: string\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Set up dotnet\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: '9'\n\n      - name: Restore dependencies\n        shell: bash\n        run: |\n          dotnet restore\n\n      - name: Build\n        shell: bash\n        run: |\n          dotnet build\n```\n\n```yaml\n# .github/workflows/pull-request.yml\nname: Pull request\n\non:\n  pull_request:\n    branches:\n      - '*'\n\njobs:\n  build:\n    # uses: \u003corg\u003e/\u003crepo\u003e/\u003cpath-to-workflow\u003e@\u003cref\u003e\n    uses: \u003cmy-org\u003e/\u003cthis-repo\u003e/.github/workflows/reusable-build.yml@main\n```\n\nReusable workflows can be used in the same repository or from other repositories. If repository is private, its possible to use it only within the organization or user space.\n\n### Inputs\n\nInputs are available only for workflow triggers `on.workflow_call` and `on.workflow_dispatch`.\n\n`on.workflow_call`:\n\n```yaml\non:\n  workflow_call:\n    inputs:\n      username:\n        description: 'A username passed from the caller workflow'\n        default: 'john-doe'\n        required: false\n        type: string\n\njobs:\n  print-username:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Print the input name to STDOUT\n        run: echo The username is ${{ inputs.username }}\n```\n\n`on.workflow_dispatch`:\n\n_For example manual trigger of workflow via GitHub UI._\n\n```yaml\non:\n  workflow_dispatch:\n    inputs:\n      logLevel:\n        description: 'Log level'\n        required: true\n        default: 'warning'\n        type: choice\n        options:\n          - info\n          - warning\n          - debug\n      print_tags:\n        description: 'True to print to STDOUT'\n        required: true\n        type: boolean\n      tags:\n        description: 'Test scenario tags'\n        required: true\n        type: string\n      environment:\n        description: 'Environment to run tests against'\n        type: environment\n        required: true\n\njobs:\n  print-tag:\n    runs-on: ubuntu-latest\n    if: ${{ inputs.print_tags }}\n    steps:\n      - name: Print the input tag to STDOUT\n        run: echo  The tags are ${{ inputs.tags }}\n```\n\n### Outputs\n\nA job can specify outputs, that can later by used by other jobs.\n\nOutputs can also be specified on the workflow, which is useful when using [Reusable workflows](#reusable-workflows).\n\n```yaml\njobs:\n  job1:\n    runs-on: ubuntu-latest\n    # Map a step output to a job output\n    outputs:\n      output1: ${{ steps.step1.outputs.test }}\n      output2: ${{ steps.step2.outputs.test }}\n    steps:\n      - id: step1\n        run: echo \"test=hello\" \u003e\u003e \"$GITHUB_OUTPUT\"\n      - id: step2\n        run: echo \"test=world\" \u003e\u003e \"$GITHUB_OUTPUT\"\n  job2:\n    runs-on: ubuntu-latest\n    needs: job1\n    steps:\n      - env:\n          OUTPUT1: ${{needs.job1.outputs.output1}}\n          OUTPUT2: ${{needs.job1.outputs.output2}}\n        run: echo \"$OUTPUT1 $OUTPUT2\"\n```\n\n## GitHub made actions\n\nTo made several actions for you to get started with. All of those actions can be found on GitHub under the organization actions, see [github.com/actions](https://github.com/actions) or [Marketplace](https://github.com/marketplace?type=actions).\n\n## Starter workflows\n\nIn order to not start from zero every time, GitHub has made a large collection of examples that you can start with. See [github.com/actions/starter-workflows](https://github.com/actions/starter-workflows).\n\n## Writing actions\n\nActions can be created in three ways:\n\n- **YAML**: The simplest option, write an `action.yml` file containing the workflow logic.\n\n    The Action can be in any directory inside a repository, but the filename must always be `action.yml`.\n\n    Such action is also called \"shell action\".\n\n    Example:\n\n    ```yaml\n    # action.yaml\n    name: 'Ship package'\n    description: 'Ships package to release server'\n\n    branding:\n      color: green\n      icon: package\n\n    inputs:\n      package-name:  # id of input\n        required: true\n        type: string\n\n    outputs:\n     url:\n       description: \"Package URL\"\n       value: ${{ steps.upload.outputs.URL }}\n\n    runs:\n      steps:\n        - name: Prepare package\n          run: echo \"Preparing package...\"\n          shell: bash\n\n        - name: Upload package\n          id: upload\n          run: |\n            echo \"Uploading package...\"\n            # Upload with cURL for example\n            echo \"Package uploaded\"\n            echo \"URL=some-url-from-upload\" \u003e\u003e $GITHUB_OUTPUT\n    ```\n\n- **Docker**: Docker action is the second simplest, but requires Docker engine to run, which may be troublesome with self-hosted runners, since Docker engine requires elevated privileges.\n\n    For example running such Actions on Kubernetes is especially troublesome because of the privileged containers, which is usually not allowed by the security team.\n\n    Such action is also called \"docker action\".\n\n- **JavaScript**: The last option is to write JavaScript code using the [Actions toolkit](https://github.com/actions/toolkit) which is a collection of multiple npm packages.\n\n### Composite actions\n\nComposite actions allow you to collect a series of workflow job steps into a single action which you can then run as a single job step in multiple workflows.\n\nIn your workflow you reference only one action in one step, but the action itself is made of multiple steps.\n\n```yaml\n# action.yml\nname: 'Hello World'\ndescription: 'Greet someone'\n\ninputs:\n  who-to-greet:  # id of input\n    description: 'Who to greet'\n    required: true\n    default: 'World'\n\noutputs:\n  random-number:\n    description: \"Random number\"\n    value: ${{ steps.random-number-generator.outputs.random-number }}\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: Set Greeting\n      run: echo \"Hello $INPUT_WHO_TO_GREET.\"\n      shell: bash\n      env:\n        INPUT_WHO_TO_GREET: ${{ inputs.who-to-greet }}\n\n    - name: Random Number Generator\n      id: random-number-generator\n      run: echo \"random-number=$(echo $RANDOM)\" \u003e\u003e $GITHUB_OUTPUT\n      shell: bash\n\n    - name: Set GitHub Path\n      run: echo \"$GITHUB_ACTION_PATH\" \u003e\u003e $GITHUB_PATH\n      shell: bash\n      env:\n        GITHUB_ACTION_PATH: ${{ github.action_path }}\n\n    - name: Run goodbye.sh\n      run: goodbye.sh\n      shell: bash\n```\n\n## Linting\n\nActions and workflows can (and should!) be linted by some automated tool.\n\n[github.com/rhysd/actionlint](https://github.com/rhysd/actionlint) is a great tool for linting actions. _actionlint_ is a Go binary that you can install locally.\n\nThis way you can avoid some mistakes.\n\nIt is also very useful to lint actions in workflows, when you are building internal composable Actions and Workflows.\n\n**Install on macOS**:\n\n```bash\nbrew install actionlint\n```\n\n**Install on Windows**:\n\n```cmd\n# chocolatey\nchoco install actionlint\n\n# scoop\nscoop install actionlint\n\n# winget\nwinget install actionlint\n```\n\n**Install on Linux**:\n\n```bash\n# Arch\npacman -S actionlint\n\n# Nix\n## NixOS\nnix-env -iA nixos.actionlint\n\n## Non-NixOS\nnix-env -iA nixpkgs.actionlint\n```\n\nOr download prebuilt binaries for your platform from the [release page](https://github.com/rhysd/actionlint/releases).\n\n## Testing\n\n### Testing actions\n\nWhen writing actions, the easiest actions to test are the ones written in JavaScript. Because you can use JavaScript test runners such as Jest or Vitest to test them like any other code.\n\nFor other types of actions (YAML and Docker), it is best to write a simple \"test workflow\" to actually run the Action.\n\n### Testing workflows\n\nFor testing Workflows, you do not have many options. Either run them locally via _act_ (see [Running workflows locally](#running-workflows-locally)) or make changes, push, trigger the workflow, and wait for results. The later option is quite time consuming and tedious.\n\n### Running workflows locally\n\n[github.com/nektos/act](https://github.com/nektos/act) is an open-source and lightweight program that can run your Workflows locally. Which tremendously simplifies testing. Instead of hacking the workflow triggers and waiting for it to finish, run the workflows locally without commit spam in the repository.\n\n\u003e `act` depends on `docker` (exactly Docker Engine API) to run workflows in containers. As long you don't require container isolation, you can run selected (e.g. windows or macOS) jobs directly on your System, see [Runners](https://nektosact.com/usage/runners.html). In the latter case you don't need to have docker installed or running.\n\n**Install on macOS**:\n\n```bash\nbrew install act\n```\n\n**Install on Windows**:\n\n```cmd\n# chocolatey\nchoco install act-cli\n\n# scoop\nscoop install act\n\n# winget\nwinget install nektos.act\n```\n\n**Install on Linux**:\n\nOr download prebuilt binaries from the [Install page](https://nektosact.com/installation/index.html#manual-download-of-prebuilt-executable).\n\n## Keeping actions up to date\n\nTo keep your actions and workflows up to date, consider using something like [Dependabot](https://github.com/dependabot) or [Renovate](https://docs.renovatebot.com/) to automatically receive Pull requests to update your actions/workflows.\n\n**Dependabot config**:\n\n```yaml\n# .github/dependabot.yml\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      # Check for updates to GitHub Actions every week\n      interval: \"weekly\"\n```\n\nFor more information, see GitHub docs: [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot)\n\n**Renovate config**:\n\nBy default, there is no need to configure Renovate, the default config automatically includes GitHub workflow files.\n\nFor more information, see Renovate docs: [Automated Dependency Updates for GitHub Actions](https://docs.renovatebot.com/modules/manager/github-actions/).\n\n## GitHub Enterprise Server considerations\n\nGitHub Actions as a feature is available on the Enterprise Server, but in order to use GitHub.com actions, you have to setup GitHub Connect. That your Enterprise Sever can browse and use actions published on GitHub.com. If not, you are left to write actions on your own.\n\n## Best practices\n\n- Use reusable workflows, to keep your workflows [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself), see [Reusable workflows](#reusable-workflows)\n- Reference at least major versions of actions (using the `@v4`) to ensure stability\n- Use Renovate or Dependabot to keep your actions up-to-date, see [Keeping actions up to date](#keeping-actions-up-to-date)\n- Use least-privileged `GITHUB_TOKEN` secret: do not add unnecessary permissions to it, see [Permissions](#permissions) and [GitHub token `secrets.GITHUB_TOKEN`](#github-token-secretsgithub_token)\n- Use workflows for other operations like labeling issues and pull requests\n- Lint your workflows to avoid mistakes, see [Linting](#linting)\n- Test your workflows locally first, before pushing, see [Running workflows locally](#running-workflows-locally)\n- Name your steps for improved readability and easier debugging\n\n## Thank you! \u0026 Questions?\n\nThat's all, thank you for your attention.\n\nQuestions?\n\nLet's go for a beer :beers:.\n\n## Vojtěch Mareš\n\n- email: [vojtech@mares.cz](mailto:vojtech@mares.cz)\n- web: [mares.cz](https://www.mares.cz)\n- x (twitter): [@vojtechmares_](https://x.com/vojtechmares_)\n- linkedin: [/in/vojtech-mares](https://www.linkedin.com/in/vojtech-mares/)\n\nDid you like the course? Tweet a recommendation on X (Twitter) and tag me\n([@vojtechmares_]((https://x.com/vojtechmares_))) and/or add me on Linked In and I will send you a request for\nrecommendation. Thanks!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvojtechmares%2Fgithub-actions-training","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvojtechmares%2Fgithub-actions-training","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvojtechmares%2Fgithub-actions-training/lists"}