Nobody likes repetitive tasks. So why should you run all your tests manually with every new code or—even worse—check that the new code didn’t break your build process? Yeah, I know you want to spend your precious time on shiny features without having to waste hours tweaking your build pipeline. Or maybe you have excuses like “I would have to pay for the automation” or “It’s time-consuming to prepare pipelines, and I don’t have time for it.” It’s not necessary to spend hours setting up your automation pipelines. Check out this guide and spin up a GitHub Action for your favorite stack fast—like Stack Overflow copy & paste fast. TL;DR If you are familiar with GitHub Actions, you can skip the theory and jump to the specific stack in the Examples section below. Continuous Integration and Continuous Delivery Let’s start with the basics. The terms might sound very technical at first. However, the basic concept is quite simple. is the automation of building and testing. This means that typically, with some code changes, the machine checks whether it’s possible to build the project and whether your tests are passing. is a little bit broader. To sum it up, this automation process will deploy or publish your code to various environments. This might be a little bit abstract and connected with the nature of your project, but you can visualize it as publishing to package registries like NPM or Nuget—or deploying your site to a staging or production environment. Continuous integration Continuous delivery GitHub Actions GitHub Actions are the feature of GitHub that consists of the API and an environment for running your tasks. You just need to create a YAML file at a specific location in the repository. The configuration .yml file contains all the specific information about the environment, such as , , or . Additionally, you can choose the environment where you want to run your tasks—it might be Linux (Ubuntu), Windows (Windows Server), or even macOS (Big Sur or Catalina). trigger events jobs strategies The anatomy of the Action Each action is represented by a file located directly in the repository at the . To be able to run it, you need to allow the Actions feature in the repository’s Settings. .yml .github/workflows location Note: If you are not familiar with the YAML format, for the purposes of the GitHub Actions, you just need to know that key-value pairs are represented by key: value . The quotes for string values are optional. Nesting of objects is done by indentation (typically two spaces), and items of the array are denoted by a dash. key: value nested_property: Nested value my_array: - First Item - Second Item GitHub Actions basic syntax keys - The name of the workflow. name - Is required, specifies events when you want to run your action. on - A job is a unit of work; jobs run in parallel by default. Each job runs in a runner environment specified by runs-on. jobs - Is required, specifies the type of the machine to run the job. jobs.<job_id>.runs-on - A step is an individual task that can run commands in a job. A step can be either an or a shell command. Each step in a job executes on the same runner, allowing the actions in that job to share data with each other ( ). jobs.<job_id>.steps action definition by GitHub - Selects an action to run as part of a step in your job. This is beneficial for reusing existing actions. You can check existing actions in the official or on the . jobs.<job_id>.steps[*].uses actions repository marketplace - Provides a shell where you can run your commands. jobs.<job_id>.runs-on Hello World Action Let’s write the action that runs every day. This action will run on Ubuntu, using code from our repository, and will run our main.py script on the environment with Python 3.8. The explanation of each line is described in the respective comment directly in the code. the Python script Create in the root of your repository with content . main.py print("Hello, World!") In the create the file with the following content. .github/workflows minimal-action.yml name: Hello World From Python # Represents the name of the whole action. on: # Specifies the section where we describe our build triggers. schedule: # Specifies the section where we describe the schedule of running the action. - cron: '* 1 * * *' # The CRON expression describing when to run the action. workflow_dispatch: # Adds the ability to run the action manually by button on the Actions tab. jobs: # Specifies the section where we describe our jobs. hello_world_job: # Specific job section. name: A greeting job # The name of the job. runs-on: ubuntu-latest # Describes the environment. steps: # Specifies the section where we describe the job's steps. - name: Checkout # The name of the step. uses: actions/checkout@v2 # Using already existing action actions/checkout@v2. This action provides us with access to the code of the repository. - name: Set up Python 3.8 # The name of the step. uses: actions/setup-python@v1 # Using already existing action actions/setup-python@v1. This action sets up a Python environment for us. with: # Configuration of the python action. python-version: 3.8 # Specific python version. - name: Run the script # The name of the step. run: python main.py # Command for running our main.py. Real-world examples It’s not possible to cover all capabilities and combinations here. Nevertheless, in this section, you can find some real-world examples—you can choose them for scaffolding your Action quickly for your specific stack and use case. They showcase configuration for various languages, platforms, and publishing to external package repositories. However, before we start, we’ll need some more commands for these real-world examples. - Runs the action when a specific release event occurs. on.release.types - Runs an action when push or pull request event occurs on a specific branch. on.<push|pull_request>.<branches|tags> - Allows to set different job configurations. jobs.<job_id>.strategy.matrix - Context for specified environment variables. env - Specifies the location of stored . It’s a good practice to store secrets and keys to storage of the repository. env.NODE_AUTH_TOKEN NPM_TOKEN Encrypted Secrets - The job will run if the condition is met. jobs.<job_id>.if Build Node.js app and publish to the NPM registry The Action that builds the Node.js project written in TypeScript and publishes the package to the NPM registry when the release is created. The publishing of the NPM package is performed by the command. npm publish on: release: types: [published] name: publish-to-npm jobs: publish: runs-on: ubuntu-latest strategy: matrix: node-version: [14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - run: npm install - run: npm run build - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_API_KEY }} Build Ruby website and publish it to GitHub Pages The action that runs with push or pulls request to master. The action builds the Jekyll project and publishes it to a specific folder. After running this action, the site is accessible on . GitHub Pages name: Ruby on: push: branches: [master] pull_request: branches: [master] jobs: build: runs-on: ubuntu-latest strategy: matrix: ruby-version: ["2.6"] env: PROJECT_ID: ${{ secrets.PROJECT_ID }} steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Update gems run: gem update --system - name: Install required packages run: gem install jekyll bundler kontent-jekyll - name: Build the output run: bundle exec jekyll build env: JEKYLL_ENV: production - name: Deploy 🚀 if: github.ref == 'refs/heads/master' # Checks if the current branch is master. uses: JamesIves/github-pages-deploy-action@3.6.2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages FOLDER: _site CLEAN: true # Automatically remove deleted files from the deploy branch Build the Ruby project and publish it to RubyGems The Ruby project is built and published to . This example uses action for publishing. RubyGems dawidd6/action-publish-gem name: publish-gem on: release: types: [published] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - name: Install dependencies run: bundle install - name: Run tests run: bundle exec rake - name: Publish gem uses: dawidd6/action-publish-gem@v1 with: api_key: ${{secrets.RUBYGEMS_API_KEY}} Node.js app with tests The tests are performed by executing the command. npm run test:all name: Test on: [pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [14] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm i - run: npm run test:all Node.js app with UI tests using a browser Sometimes the test environment requires a browser, and default virtual environments come with a lot of . This action runs tests directly in the browser using the command. preinstalled browsers npm run test:all name: Test on: [pull_request] jobs: build: runs-on: windows-latest strategy: matrix: node-version: [14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm i - run: npm run test:all PHP project using Composer and Codecov This action sets up the PHP environment on the Ubuntu machine using . Moreover, it validates and caches packages. In the end, it runs a code coverage tool by . shivammathur/setup-php@v2 Composer Codecov name: Build & Test & Report on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: php-versions: ['7.0', '7.1' , '7.2' , '7.3' ] phpunit-versions: ['latest'] steps: - uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: mbstring, intl ini-values: post_max_size=256M, max_execution_time=180 coverage: xdebug tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }} - name: Validate composer.json and composer.lock run: composer validate --strict - name: Cache Composer packages id: composer-cache uses: actions/cache@v2 with: path: vendor key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} restore-keys: | $ -php- {{ runner.os }} - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - name: Coverage run: vendor/bin/phpunit --coverage-clover coverage.xml - name: Codecov uses: codecov/codecov-action@v1 Build, test, and publish to Nuget .NET Core package This example covers the release process of the .NET Core SDK package. The action restores packages, builds the solution, runs tests, and publishes artifacts to . Nuget name: Publish to NuGet on: release: types: [published] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0 .x - name: Extract version from tag id: get_version uses: battila7/get-version-action@v2 - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore --configuration Release /p:ContinuousIntegrationBuild=true /p:Version="${{ steps.get_version.outputs.version-without-v }}" - name: Test run: dotnet test --no-build --verbosity normal --configuration Release /p:CollectCoverage=true /p:CoverletOutputFormat=opencover - name: Codecov uses: codecov/codecov-action@v1 - name: Pack run: dotnet pack --no-build --include-symbols --verbosity normal --configuration Release --output ./artifacts /p:PackageVersion="${{ steps.get_version.outputs.version-without-v }}" - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: "NuGet packages" path: ./artifacts - name: Publish artifacts to NuGet.org run: dotnet nuget push './artifacts/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${NUGET_API_KEY} --skip-duplicate env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - name: Upload artifacts to the GitHub release uses: Roang-zero1/github-upload-release-artifacts-action@v2.1.0 with: args: ./artifacts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Build, test, and publish Java project to Nexus registry In this Action, the package is built and published using . The package is deployed to the repository. Gradle Nexus name: Publish package to the Maven Central Repository on: release: types: [created] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Java uses: actions/setup-java@v1 with: java-version: 1.8 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Version release run: echo Releasing verion ${{ github.event.release.tag_name }} - name: Publish package run: ./gradlew publish env: RELEASE_TAG: ${{ github.event.release.tag_name }} NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} Build test and publish to Cocoapods project written in Swift At first, the dependencies are restored. Afterward, the project is built, and the Action runs tests. In the end, the package is pushed to the Cocoapods repository using the command. Cocoapods pod trunk push on: release: types: [published] name: publish-to-cocoapods jobs: publish-to-cocoapods: name: publish-to-cocoapods runs-on: macOS-latest strategy: matrix: destination: ['platform=iOS Simulator,OS=12.2,name=iPhone 11 '] steps: - name: Checkout uses: actions/checkout@master - name: Build and test run: | gem install cocoapods pod repo update pod install --project-directory=Example set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/KenticoKontentDelivery.xcworkspace -scheme KenticoKontentDelivery-Example -sdk iphonesimulator -destination ' name=iPhone 11 ' ONLY_ACTIVE_ARCH=NO | xcpretty - name: Publish Cocoapod run: pod trunk push env: COCOAPODS_TRUNK_TOKEN: $ {{ secrets.COCOAPODS_TRUNK_TOKEN }} Bonus: GitHub Actions not only for CI/CD automation Not only can GitHub Actions be used for automating your build, running tests, and release pipelines but you can also leverage them for scheduling automatic tasks, such as web scraping or automatic update routines. This last bonus action runs the Python script at a specific time. The Python script does some automation—in this case, it scrapes the HTML page and makes data readable for machine processing. The output of the script is then committed and pushed to the specified GIT repository. name: Python application on: push: branches: [ master ] pull_request: branches: [ master ] schedule: - cron: '0 0 * * *' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v1 with: python-version: 3.8 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Lint with flake8 run: | pip install flake8 # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Run script run: | python stocks.py - name: Deploy page run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add docs/index.html -f git commit -m "Update docs/index.html" -a git push "https://makma:$ @github.com/makma/stocks-movement.git" HEAD:master --follow-tags {{ secrets.GITHUB_PERSONAL_TOKEN }} Debrief I believe GitHub Actions can be utilized for many different scenarios. You can find the majority of the example actions above in real-world use—they were based on and taken over from open-source . Feel free to take a look, get inspired, or maybe improve them ;). Didn’t you find your use case in the examples above, or did I not include your favorite GitHub Actions feature? Let me know on or . I’ll be happy to discuss your use case and update the article. Kentico Kontent’s GitHub Twitter Discord