{
  "data": {
    "api_url": "https://londo.me/blog/api/v1/posts/github-actions-beyond-ci.json",
    "code": {
      "blocks": 6,
      "languages": [
        "yaml"
      ]
    },
    "content_html": "<p>Most people meet GitHub Actions as &ldquo;the thing that runs tests on pull requests.&rdquo; But under the hood it&rsquo;s much more general: a computer that wakes up when something happens, runs whatever you tell it, and goes back to sleep. For public repos, it&rsquo;s free.</p>\n<p>Here are things you can do with it that have nothing to do with CI.</p>\n<h2 id=\"how-it-works-in-30-seconds\">How it works in 30 seconds</h2>\n<p>A workflow is a YAML file in <code>.github/workflows/</code>. It has:</p>\n<ul>\n<li><strong>triggers</strong> (<code>on:</code>): a push, a new issue, a schedule, a button click</li>\n<li><strong>jobs</strong> that run on a fresh virtual machine</li>\n<li><strong>steps</strong>, each either a shell command or a reusable action</li>\n</ul>\n<p>That&rsquo;s it. Anything you can script, you can trigger.</p>\n<h2 id=\"1-host-a-website\">1. Host a website</h2>\n<p>This blog is built by Actions. Every push to <code>main</code> builds the site with Hugo and publishes it to GitHub Pages. No server, no bill.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"line\"><span class=\"cl\"><span class=\"nt\">on</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">push</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"nt\">branches</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"l\">main]</span><span class=\"w\">\n</span></span></span></code></pre></div><p>The official <code>actions/upload-pages-artifact</code> and <code>actions/deploy-pages</code> actions do the publishing.</p>\n<h2 id=\"2-run-things-on-a-schedule\">2. Run things on a schedule</h2>\n<p>Actions supports cron:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"line\"><span class=\"cl\"><span class=\"nt\">on</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">schedule</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span>- <span class=\"nt\">cron</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">&#34;0 14 * * 1&#34;</span><span class=\"w\">   </span><span class=\"c\"># every Monday at 14:00 UTC</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">workflow_dispatch</span><span class=\"p\">:</span><span class=\"w\">        </span><span class=\"c\"># plus a manual &#34;Run&#34; button</span><span class=\"w\">\n</span></span></span></code></pre></div><p>Uses: nightly data scrapes, weekly reports, checking whether a website is up, rebuilding a site so &ldquo;posted 3 days ago&rdquo; stays accurate.</p>\n<p><strong>Know this:</strong></p>\n<ul>\n<li>Times are in <strong>UTC</strong>.</li>\n<li>Scheduled runs can start late when GitHub is busy, sometimes by many minutes. Don&rsquo;t use it for anything that must happen at an exact second.</li>\n<li>In public repos, scheduled workflows are <strong>automatically disabled after 60 days without repository activity</strong>. GitHub emails you first.</li>\n</ul>\n<h2 id=\"3-keep-a-file-updated-automatically\">3. Keep a file updated automatically</h2>\n<p>A workflow can edit files and commit them back. For example, a profile README that always lists your latest blog posts:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"line\"><span class=\"cl\"><span class=\"nt\">permissions</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">contents</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l\">write</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"nt\">jobs</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">update</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"nt\">runs-on</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l\">ubuntu-latest</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"nt\">steps</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">      </span>- <span class=\"nt\">uses</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l\">actions/checkout@v7</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">      </span>- <span class=\"nt\">run</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l\">./scripts/update-readme.sh</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">      </span>- <span class=\"nt\">run</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">|</span><span class=\"sd\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"sd\">          git config user.name &#34;github-actions[bot]&#34;\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"sd\">          git config user.email &#34;41898282+github-actions[bot]@users.noreply.github.com&#34;\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"sd\">          git add README.md\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"sd\">          git diff --cached --quiet || git commit -m &#34;Update latest posts&#34;\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"sd\">          git push</span><span class=\"w\">\n</span></span></span></code></pre></div><p>The <code>git diff --cached --quiet ||</code> part only commits if something actually changed, so you don&rsquo;t get empty commits.</p>\n<h2 id=\"4-manage-issues-and-pull-requests\">4. Manage issues and pull requests</h2>\n<ul>\n<li><strong>Auto-label</strong> PRs by which files they touch (<code>actions/labeler</code>).</li>\n<li><strong>Welcome first-time contributors</strong> with a friendly comment.</li>\n<li><strong>Close stale issues</strong> after a period of inactivity (<code>actions/stale</code>). Use gently; closing people&rsquo;s issues automatically can feel rude.</li>\n<li><strong>Enforce PR title formats</strong> for changelogs.</li>\n</ul>\n<p>Triggers like <code>issues</code>, <code>issue_comment</code>, and <code>pull_request</code> make this easy.</p>\n<h2 id=\"5-build-a-chatops-bot\">5. Build a ChatOps bot</h2>\n<p>A workflow can react to comments. Someone types <code>/deploy staging</code> on a PR, and a workflow checks their permissions and runs the deploy. Check the comment author&rsquo;s permission level before doing anything, or anyone on the internet can trigger it.</p>\n<h2 id=\"6-watch-other-things-on-the-internet\">6. Watch other things on the internet</h2>\n<p>A scheduled job can:</p>\n<ul>\n<li>check whether your site returns 200 and open an issue if not</li>\n<li>check whether a dependency released a new version</li>\n<li>watch a page for changes and notify you</li>\n</ul>\n<p>GitHub isn&rsquo;t built for real-time monitoring, but for &ldquo;check once an hour and tell me,&rdquo; it&rsquo;s plenty.</p>\n<h2 id=\"7-trigger-workflows-from-outside-github\">7. Trigger workflows from outside GitHub</h2>\n<p>With <code>repository_dispatch</code>, anything that can send an HTTP request with a GitHub token can start a workflow: a script on your server, a webhook from another service, a phone shortcut. The token needs write access to the repo, so treat it like a password.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"line\"><span class=\"cl\"><span class=\"nt\">on</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">repository_dispatch</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"nt\">types</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"l\">rebuild-site]</span><span class=\"w\">\n</span></span></span></code></pre></div><h2 id=\"8-generate-release-artifacts\">8. Generate release artifacts</h2>\n<p>Build binaries for Windows, macOS, and Linux in parallel with a matrix, then attach them to a GitHub Release automatically when you push a version tag.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"line\"><span class=\"cl\"><span class=\"nt\">on</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"nt\">push</span><span class=\"p\">:</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"nt\">tags</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">&#34;v*&#34;</span><span class=\"p\">]</span><span class=\"w\">\n</span></span></span></code></pre></div><h2 id=\"things-you-should-know\">Things you should know</h2>\n<p><strong>Least privilege.</strong> Set <code>permissions:</code> explicitly in every workflow. Give each workflow only what it needs.</p>\n<p><strong>Secrets and forks.</strong> Workflows triggered by pull requests from forks don&rsquo;t get your secrets, on purpose. Be very careful with the <code>pull_request_target</code> trigger: it runs with your secrets and write permissions, and if it checks out and runs the fork&rsquo;s code, a stranger&rsquo;s PR can steal your secrets.</p>\n<p><strong>Pin actions.</strong> A third-party action is someone else&rsquo;s code running with your permissions. Pin it to a full commit hash rather than a tag if you don&rsquo;t fully trust the author:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"line\"><span class=\"cl\">- <span class=\"nt\">uses</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l\">some-author/some-action@8f4b7f84864484a7bf31766abe9204da3cbe65b3</span><span class=\"w\">\n</span></span></span></code></pre></div><p><strong>Free isn&rsquo;t unlimited.</strong> Public repos get free minutes on standard runners. Private repos get a monthly allowance, then usage is billed. Check the current limits before you build something heavy.</p>\n<h2 id=\"closing-thought\">Closing thought</h2>\n<p>Think of every repo as coming with a small, free, scriptable computer that listens for events. CI is one thing you can do with it. Your imagination and the terms of service are the rest.</p>\n",
    "content_text": "Most people meet GitHub Actions as “the thing that runs tests on pull requests.” But under the hood it’s much more general: a computer that wakes up when something happens, runs whatever you tell it, and goes back to sleep. For public repos, it’s free.\nHere are things you can do with it that have nothing to do with CI.\nHow it works in 30 seconds A workflow is a YAML file in .github/workflows/. It has:\ntriggers (on:): a push, a new issue, a schedule, a button click jobs that run on a fresh virtual machine steps, each either a shell command or a reusable action That’s it. Anything you can script, you can trigger.\n1. Host a website This blog is built by Actions. Every push to main builds the site with Hugo and publishes it to GitHub Pages. No server, no bill.\non: push: branches: [main] The official actions/upload-pages-artifact and actions/deploy-pages actions do the publishing.\n2. Run things on a schedule Actions supports cron:\non: schedule: - cron: \"0 14 * * 1\" # every Monday at 14:00 UTC workflow_dispatch: # plus a manual \"Run\" button Uses: nightly data scrapes, weekly reports, checking whether a website is up, rebuilding a site so “posted 3 days ago” stays accurate.\nKnow this:\nTimes are in UTC. Scheduled runs can start late when GitHub is busy, sometimes by many minutes. Don’t use it for anything that must happen at an exact second. In public repos, scheduled workflows are automatically disabled after 60 days without repository activity. GitHub emails you first. 3. Keep a file updated automatically A workflow can edit files and commit them back. For example, a profile README that always lists your latest blog posts:\npermissions: contents: write jobs: update: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - run: ./scripts/update-readme.sh - run: | git config user.name \"github-actions[bot]\" git config user.email \"41898282+github-actions[bot]@users.noreply.github.com\" git add README.md git diff --cached --quiet || git commit -m \"Update latest posts\" git push The git diff --cached --quiet || part only commits if something actually changed, so you don’t get empty commits.\n4. Manage issues and pull requests Auto-label PRs by which files they touch (actions/labeler). Welcome first-time contributors with a friendly comment. Close stale issues after a period of inactivity (actions/stale). Use gently; closing people’s issues automatically can feel rude. Enforce PR title formats for changelogs. Triggers like issues, issue_comment, and pull_request make this easy.\n5. Build a ChatOps bot A workflow can react to comments. Someone types /deploy staging on a PR, and a workflow checks their permissions and runs the deploy. Check the comment author’s permission level before doing anything, or anyone on the internet can trigger it.\n6. Watch other things on the internet A scheduled job can:\ncheck whether your site returns 200 and open an issue if not check whether a dependency released a new version watch a page for changes and notify you GitHub isn’t built for real-time monitoring, but for “check once an hour and tell me,” it’s plenty.\n7. Trigger workflows from outside GitHub With repository_dispatch, anything that can send an HTTP request with a GitHub token can start a workflow: a script on your server, a webhook from another service, a phone shortcut. The token needs write access to the repo, so treat it like a password.\non: repository_dispatch: types: [rebuild-site] 8. Generate release artifacts Build binaries for Windows, macOS, and Linux in parallel with a matrix, then attach them to a GitHub Release automatically when you push a version tag.\non: push: tags: [\"v*\"] Things you should know Least privilege. Set permissions: explicitly in every workflow. Give each workflow only what it needs.\nSecrets and forks. Workflows triggered by pull requests from forks don’t get your secrets, on purpose. Be very careful with the pull_request_target trigger: it runs with your secrets and write permissions, and if it checks out and runs the fork’s code, a stranger’s PR can steal your secrets.\nPin actions. A third-party action is someone else’s code running with your permissions. Pin it to a full commit hash rather than a tag if you don’t fully trust the author:\n- uses: some-author/some-action@8f4b7f84864484a7bf31766abe9204da3cbe65b3 Free isn’t unlimited. Public repos get free minutes on standard runners. Private repos get a monthly allowance, then usage is billed. Check the current limits before you build something heavy.\nClosing thought Think of every repo as coming with a small, free, scriptable computer that listens for events. CI is one thing you can do with it. Your imagination and the terms of service are the rest.",
    "cover": {
      "alt": "A terminal printing a scheduled GitHub Actions workflow, above the post title",
      "height": 630,
      "url": "https://londo.me/blog/posts/github-actions-beyond-ci/cover.png",
      "width": 1200
    },
    "description": "GitHub Actions is a free, event-driven computer attached to every repo. Running tests is just the start.",
    "headings": [
      {
        "id": "how-it-works-in-30-seconds",
        "level": 2,
        "title": "How it works in 30 seconds",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#how-it-works-in-30-seconds"
      },
      {
        "id": "1-host-a-website",
        "level": 2,
        "title": "1. Host a website",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#1-host-a-website"
      },
      {
        "id": "2-run-things-on-a-schedule",
        "level": 2,
        "title": "2. Run things on a schedule",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#2-run-things-on-a-schedule"
      },
      {
        "id": "3-keep-a-file-updated-automatically",
        "level": 2,
        "title": "3. Keep a file updated automatically",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#3-keep-a-file-updated-automatically"
      },
      {
        "id": "4-manage-issues-and-pull-requests",
        "level": 2,
        "title": "4. Manage issues and pull requests",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#4-manage-issues-and-pull-requests"
      },
      {
        "id": "5-build-a-chatops-bot",
        "level": 2,
        "title": "5. Build a ChatOps bot",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#5-build-a-chatops-bot"
      },
      {
        "id": "6-watch-other-things-on-the-internet",
        "level": 2,
        "title": "6. Watch other things on the internet",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#6-watch-other-things-on-the-internet"
      },
      {
        "id": "7-trigger-workflows-from-outside-github",
        "level": 2,
        "title": "7. Trigger workflows from outside GitHub",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#7-trigger-workflows-from-outside-github"
      },
      {
        "id": "8-generate-release-artifacts",
        "level": 2,
        "title": "8. Generate release artifacts",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#8-generate-release-artifacts"
      },
      {
        "id": "things-you-should-know",
        "level": 2,
        "title": "Things you should know",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#things-you-should-know"
      },
      {
        "id": "closing-thought",
        "level": 2,
        "title": "Closing thought",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/#closing-thought"
      }
    ],
    "images": [
      {
        "height": 630,
        "is_cover": true,
        "name": "cover.png",
        "url": "https://londo.me/blog/posts/github-actions-beyond-ci/cover.png",
        "width": 1200
      }
    ],
    "license": {
      "code": "MIT",
      "writing": "CC-BY-4.0"
    },
    "links": {
      "external": [],
      "internal": []
    },
    "newer": null,
    "older": {
      "api_url": "https://londo.me/blog/api/v1/posts/git-internals.json",
      "slug": "git-internals",
      "title": "Your .git Folder Is a Database. Let's Read It.",
      "url": "https://londo.me/blog/posts/git-internals/"
    },
    "published": "2026-10-22T09:00:00-07:00",
    "reading_time_minutes": 4,
    "slug": "github-actions-beyond-ci",
    "source": {
      "history": "https://github.com/Londopy/blog/commits/main/content/posts/github-actions-beyond-ci/index.md",
      "markdown": "https://github.com/Londopy/blog/blob/main/content/posts/github-actions-beyond-ci/index.md",
      "raw": "https://raw.githubusercontent.com/Londopy/blog/main/content/posts/github-actions-beyond-ci/index.md"
    },
    "summary": "Most people meet GitHub Actions as “the thing that runs tests on pull requests.” But under the hood it’s much more general: a computer that wakes up when something happens, runs whatever you tell it, and goes back to sleep. For public repos, it’s free.\nHere are things you can do with it that have nothing to do with CI.\nHow it works in 30 seconds A workflow is a YAML file in .github/workflows/. It has:",
    "tags": [
      {
        "api_url": "https://londo.me/blog/api/v1/tags/github.json",
        "name": "github",
        "slug": "github",
        "url": "https://londo.me/blog/tags/github/"
      },
      {
        "api_url": "https://londo.me/blog/api/v1/tags/github-actions.json",
        "name": "github-actions",
        "slug": "github-actions",
        "url": "https://londo.me/blog/tags/github-actions/"
      },
      {
        "api_url": "https://londo.me/blog/api/v1/tags/automation.json",
        "name": "automation",
        "slug": "automation",
        "url": "https://londo.me/blog/tags/automation/"
      }
    ],
    "title": "Things GitHub Actions Can Do That Aren't CI",
    "updated": "2026-10-22T09:00:00-07:00",
    "url": "https://londo.me/blog/posts/github-actions-beyond-ci/",
    "word_count": 757
  },
  "meta": {
    "api": "londopy-blog",
    "docs": "https://londo.me/blog/api/",
    "generated": "2026-10-27T05:20:06Z",
    "self": "https://londo.me/blog/api/v1/latest.json",
    "version": "1.0.0"
  }
}