<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martzcode</title>
    <description>The latest articles on DEV Community by Martzcode (@martzcode).</description>
    <link>https://dev.to/martzcode</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4081049%2Fcec66f13-fb6e-4e26-b04a-a8a5a8226426.png</url>
      <title>DEV Community: Martzcode</title>
      <link>https://dev.to/martzcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martzcode"/>
    <language>en</language>
    <item>
      <title>I Built a Whole Desktop App Because I Couldn't Pick a Gacha Banner</title>
      <dc:creator>Martzcode</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:51:53 +0000</pubDate>
      <link>https://dev.to/martzcode/i-built-a-whole-desktop-app-because-i-couldnt-pick-a-gacha-banner-mfj</link>
      <guid>https://dev.to/martzcode/i-built-a-whole-desktop-app-because-i-couldnt-pick-a-gacha-banner-mfj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr53jl9ebn234zo3iclv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr53jl9ebn234zo3iclv6.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;It was 1:47 AM.&lt;/p&gt;

&lt;p&gt;I had 42 pulls, a credit card that was dangerously within reach, and two banners staring back at me. On the left, the character everyone said was meta. On the right, the one I actually liked. Both were limited. Both would disappear in a few days.&lt;/p&gt;

&lt;p&gt;I did what any reasonable person does in that situation: I opened a coin flip website.&lt;/p&gt;

&lt;p&gt;Then I closed it, because a cookie banner asked me to accept 847 partners.&lt;/p&gt;

&lt;p&gt;Then I opened another one. It wanted an account.&lt;/p&gt;

&lt;p&gt;Then I opened a random number generator that looked like it had been designed in 2003 and had a 3-second ad before showing me a number.&lt;/p&gt;

&lt;p&gt;And somewhere between the third pop-up and the fourth "accept all", I thought:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is the simplest decision in the world the hardest thing to do on a computer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That night, I didn't pull. I opened my code editor instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tiny problem behind the big project
&lt;/h2&gt;

&lt;p&gt;The gacha dilemma is silly. I know that. But it's also the perfect example of a much more common problem.&lt;/p&gt;

&lt;p&gt;We hesitate constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which of these two apartments should I take?&lt;/li&gt;
&lt;li&gt;Which candidate do we hire?&lt;/li&gt;
&lt;li&gt;Which feature do we build next?&lt;/li&gt;
&lt;li&gt;Which restaurant do we pick when nobody wants to decide?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And for all of these, we either overthink for 40 minutes, or we ask a friend who cares way less than we do.&lt;/p&gt;

&lt;p&gt;I didn't want to build "a random generator". I wanted to build &lt;strong&gt;the thing I wished existed at 1:47 AM&lt;/strong&gt;: something that opens instantly, does one job perfectly, and gets out of the way.&lt;/p&gt;

&lt;p&gt;No account. No ads. No cookie banner. No loading spinner. No internet.&lt;/p&gt;

&lt;p&gt;Just a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built Capricio
&lt;/h2&gt;

&lt;p&gt;Capricio is a small desktop app for Windows, made with &lt;strong&gt;Tauri 2&lt;/strong&gt;, &lt;strong&gt;Angular 22&lt;/strong&gt; and &lt;strong&gt;Rust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does four things, and it tries very hard to do them well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coin flip&lt;/strong&gt;: with custom labels, so you can flip between "Banner A" and "Banner B" instead of Heads and Tails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random number&lt;/strong&gt;: min/max, optional no-repeat, instant result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List draw&lt;/strong&gt;: paste a list, pick one or several winners, with or without replacement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weighted pros &amp;amp; cons&lt;/strong&gt;: list your arguments, give them a weight, and get a verdict with a score and a percentage gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is designed around one rule: &lt;strong&gt;a decision should take less than three clicks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's a short animation, a "Play again" button, full keyboard navigation, four languages (English, French, German, Spanish), light/dark/system themes, and a local history so you can look back at what you decided, and whether you actually followed through.&lt;/p&gt;

&lt;p&gt;And here's the part I'm most proud of: &lt;strong&gt;it's 100% offline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No account. No telemetry. No tracking. No ads. Your arguments, your lists, your history, they never leave your machine. Not because of a privacy policy, but because there is no server to send them to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What building it taught me
&lt;/h2&gt;

&lt;p&gt;I thought this would be a weekend project. It wasn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Rust makes "fast" feel effortless
&lt;/h3&gt;

&lt;p&gt;All the sensitive logic lives in Rust: the random draws, the weighted scoring, the bias detection when only one side of a pros/cons list is filled. Angular only renders. That separation is not just architectural purity, it's what makes the app feel instant.&lt;/p&gt;

&lt;p&gt;Cold start is under 1.5 seconds. The installer is under 15 MB (without the system WebView). Idle memory stays under 50 MB.&lt;/p&gt;

&lt;p&gt;When you're half-asleep trying to make a decision, those numbers are not specs. They're the difference between using the app and closing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The hardest part of a decision tool is trust
&lt;/h3&gt;

&lt;p&gt;If I'm going to let an app decide something for me, even something small, I need to trust it. So I unit-tested the scoring and random modules heavily. Not because it's a checklist, but because a decision helper that is subtly wrong is worse than no decision helper at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Constraints are a feature
&lt;/h3&gt;

&lt;p&gt;I removed things. A lot of things. No cloud sync in V1. No export. No social features. No "AI-powered decision engine".&lt;/p&gt;

&lt;p&gt;Every feature I added was a reason for someone to hesitate &lt;em&gt;before&lt;/em&gt; making a decision, which is exactly the opposite of the goal.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The pause is the product
&lt;/h3&gt;

&lt;p&gt;This is the one that surprised me most.&lt;/p&gt;

&lt;p&gt;People don't actually want an app to decide for them. They want permission to stop circling. The coin flip doesn't choose for you, it shows you how you feel about the result.&lt;/p&gt;

&lt;p&gt;Capricio is not an oracle. It's a &lt;strong&gt;pause button&lt;/strong&gt; with a clean interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment it became real
&lt;/h2&gt;

&lt;p&gt;A few days after publishing it, a friend texted me a screenshot.&lt;/p&gt;

&lt;p&gt;He had used the pros &amp;amp; cons screen to decide whether to accept a job offer. He told me he already knew the answer, he just needed to see it written down, weighted, and laid out in front of him.&lt;/p&gt;

&lt;p&gt;That's when it stopped being "the app I built because of a gacha banner" and became something I actually want to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Capricio is free, open source, and available now.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Store:&lt;/strong&gt; &lt;a href="https://apps.microsoft.com/detail/9pm019wd4sbw" rel="noopener noreferrer"&gt;Download Capricio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Martzcode/Capricio" rel="noopener noreferrer"&gt;Martzcode/Capricio&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's built for Windows 10/11 today, with a cross-platform architecture underneath (macOS and Linux builds are possible).&lt;/p&gt;

&lt;h3&gt;
  
  
  If you try it, two small favors
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Star the repo&lt;/strong&gt; on GitHub. It takes two seconds, and for a solo open-source project, it genuinely helps more than you'd think, it's often how other developers discover the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave a rating on the Microsoft Store&lt;/strong&gt; if the app is useful to you. Ratings are the main signal that tells new users "this is legit, and it's maintained." If something's missing or broken, tell me in the review or open an issue, I read everything.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And if you have a better idea for the next feature, open an issue. I built this because I couldn't decide between two banners. I'd love to hear what &lt;em&gt;you&lt;/em&gt; can't decide between.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;We spend so much energy trying to optimize decisions, and so little energy noticing that most of them don't need optimizing. They need a nudge.&lt;/p&gt;

&lt;p&gt;So here's my nudge to you: next time you're stuck between two options, don't open fifteen tabs. Flip a coin. Notice your reaction to the result.&lt;/p&gt;

&lt;p&gt;You'll probably have your answer before the animation even ends.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That's how Capricio was born. Not from a product strategy, but from a very tired developer, a gacha banner, and a refusal to accept one more cookie pop-up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tauri</category>
      <category>rust</category>
      <category>angular</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"What I Wish I Knew About GitHub (and Especially GitHub Actions)"</title>
      <dc:creator>Martzcode</dc:creator>
      <pubDate>Wed, 30 Sep 2026 13:33:29 +0000</pubDate>
      <link>https://dev.to/martzcode/what-i-wish-i-knew-about-github-and-especially-github-actions-59bb</link>
      <guid>https://dev.to/martzcode/what-i-wish-i-knew-about-github-and-especially-github-actions-59bb</guid>
      <description>&lt;p&gt;When I started using GitHub, I treated it as a place to &lt;strong&gt;store code&lt;/strong&gt;. Years later, I realized it's also a &lt;strong&gt;build server, a release manager, a deployment platform, and an automation engine&lt;/strong&gt;, all included in the same repo.&lt;/p&gt;

&lt;p&gt;This is the article I wish I had read at the beginning. First, a quick tour of the commands worth knowing. Then the main event: &lt;strong&gt;GitHub Actions&lt;/strong&gt; and everything you can automate with it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: GitHub essentials (the short version)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Git commands I use every day
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start from a remote repo&lt;/span&gt;
git clone https://github.com/user/repo.git

&lt;span class="c"&gt;# Work on a feature branch (never directly on main)&lt;/span&gt;
git switch &lt;span class="nt"&gt;-c&lt;/span&gt; feature/login-page

&lt;span class="c"&gt;# See what changed&lt;/span&gt;
git status
git diff

&lt;span class="c"&gt;# Stage and commit&lt;/span&gt;
git add &lt;span class="nt"&gt;-p&lt;/span&gt;                      &lt;span class="c"&gt;# stage interactively, hunk by hunk&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add login page"&lt;/span&gt;

&lt;span class="c"&gt;# Push and set upstream&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feature/login-page

&lt;span class="c"&gt;# Stay up to date&lt;/span&gt;
git pull &lt;span class="nt"&gt;--rebase&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Git commands I wish I had learned earlier
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Undo the last commit but keep your changes&lt;/span&gt;
git reset &lt;span class="nt"&gt;--soft&lt;/span&gt; HEAD~1

&lt;span class="c"&gt;# Temporarily save work in progress&lt;/span&gt;
git stash push &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"wip: login form"&lt;/span&gt;
git stash pop

&lt;span class="c"&gt;# Find which commit introduced a bug (binary search!)&lt;/span&gt;
git bisect start
git bisect bad                  &lt;span class="c"&gt;# current commit is broken&lt;/span&gt;
git bisect good v1.2.0          &lt;span class="c"&gt;# this tag was fine&lt;/span&gt;
&lt;span class="c"&gt;# Git checks out commits for you to test, then:&lt;/span&gt;
git bisect reset

&lt;span class="c"&gt;# Recover "lost" commits after a bad reset or rebase&lt;/span&gt;
git reflog

&lt;span class="c"&gt;# Work on two branches at once, in two folders&lt;/span&gt;
git worktree add ../repo-hotfix hotfix/urgent

&lt;span class="c"&gt;# Tag a release&lt;/span&gt;
git tag &lt;span class="nt"&gt;-a&lt;/span&gt; v1.0.0 &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"First stable release"&lt;/span&gt;
git push origin v1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The GitHub CLI (&lt;code&gt;gh&lt;/code&gt;): a game changer
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt; lets you do almost everything from the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh auth login                        &lt;span class="c"&gt;# authenticate once&lt;/span&gt;

gh repo create my-app &lt;span class="nt"&gt;--public&lt;/span&gt; &lt;span class="nt"&gt;--clone&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--fill&lt;/span&gt;                  &lt;span class="c"&gt;# open a PR from your current branch&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;list                           &lt;span class="c"&gt;# list open PRs&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;checkout 42                    &lt;span class="c"&gt;# test someone's PR locally&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;merge 42 &lt;span class="nt"&gt;--squash&lt;/span&gt; &lt;span class="nt"&gt;--delete-branch&lt;/span&gt;

gh issue create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Bug: login fails"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Steps to reproduce..."&lt;/span&gt;

gh release create v1.0.0 &lt;span class="nt"&gt;--generate-notes&lt;/span&gt;

gh run list                          &lt;span class="c"&gt;# list recent workflow runs&lt;/span&gt;
gh run watch                         &lt;span class="c"&gt;# follow a run live&lt;/span&gt;
gh run view &lt;span class="nt"&gt;--log-failed&lt;/span&gt;             &lt;span class="c"&gt;# show logs of failed steps only&lt;/span&gt;
gh workflow run deploy.yml           &lt;span class="c"&gt;# trigger a workflow manually&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last four commands are the ones that connect to the second part of this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repo features worth enabling early
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Branch protection rules&lt;/strong&gt;: require PR reviews and passing status checks before merging into &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/strong&gt;: automatically request reviews from the right people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue and PR templates&lt;/strong&gt; (&lt;code&gt;.github/ISSUE_TEMPLATE/&lt;/code&gt;, &lt;code&gt;.github/pull_request_template.md&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependabot&lt;/strong&gt;: automatic dependency update PRs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret scanning and push protection&lt;/strong&gt;: catches leaked keys before they hit the repo.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Part 2: GitHub Actions, the real superpower
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; is GitHub's built-in automation platform. You describe what should happen in a YAML file, and GitHub runs it on its own servers in response to &lt;strong&gt;events&lt;/strong&gt; (a push, a pull request, a tag, a schedule, a button click...).&lt;/p&gt;

&lt;p&gt;Public repositories get free minutes, and private repositories get a monthly free quota depending on your plan. Check the current limits in the &lt;a href="https://docs.github.com/en/billing" rel="noopener noreferrer"&gt;GitHub docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The vocabulary in 60 seconds
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A YAML file in &lt;code&gt;.github/workflows/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event / trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What starts the workflow (&lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull_request&lt;/code&gt;, &lt;code&gt;schedule&lt;/code&gt;...)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Job&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A group of steps running on the same machine (jobs run in parallel by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Step&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A single shell command (&lt;code&gt;run&lt;/code&gt;) or a reusable action (&lt;code&gt;uses&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The machine that executes a job (&lt;code&gt;ubuntu-latest&lt;/code&gt;, &lt;code&gt;windows-latest&lt;/code&gt;, &lt;code&gt;macos-latest&lt;/code&gt;, or self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Action&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A reusable building block, found on the &lt;a href="https://github.com/marketplace?type=actions" rel="noopener noreferrer"&gt;Marketplace&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Your first workflow
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit, push, open the &lt;strong&gt;Actions&lt;/strong&gt; tab. That's it: every push and every PR now runs your tests.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: Combine this with branch protection and you can &lt;strong&gt;block merging&lt;/strong&gt; until this workflow is green.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What you can automate with GitHub Actions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Continuous Integration with a build matrix
&lt;/h3&gt;

&lt;p&gt;Test on several versions and operating systems at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;windows-latest&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;20&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;22&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node }}&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;strong&gt;4 jobs&lt;/strong&gt; from about 15 lines. The same idea works for Python, Java, Go, Rust, .NET and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Automatic releases
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Option A: Release when you push a tag
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v*"&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zip -r dist.zip dist&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create GitHub Release&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gh release create "${{ github.ref_name }}" dist.zip \&lt;/span&gt;
            &lt;span class="s"&gt;--generate-notes \&lt;/span&gt;
            &lt;span class="s"&gt;--title "${{ github.ref_name }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now releasing is just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git tag v1.2.0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push origin v1.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub builds your project, attaches the artifact, and generates release notes from merged PRs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option B: Fully automated versioning
&lt;/h4&gt;

&lt;p&gt;Tools like &lt;a href="https://github.com/googleapis/release-please-action" rel="noopener noreferrer"&gt;&lt;strong&gt;release-please&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://github.com/semantic-release/semantic-release" rel="noopener noreferrer"&gt;&lt;strong&gt;semantic-release&lt;/strong&gt;&lt;/a&gt; read your commit messages (Conventional Commits: &lt;code&gt;feat:&lt;/code&gt;, &lt;code&gt;fix:&lt;/code&gt;, &lt;code&gt;feat!:&lt;/code&gt;) and automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bump the version,&lt;/li&gt;
&lt;li&gt;update the &lt;code&gt;CHANGELOG.md&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;open a "Release PR" (release-please) that you merge to publish.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-please&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release-please&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;googleapis/release-please-action@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;release-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Publish a package to npm
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish to npm&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;   &lt;span class="c1"&gt;# enables provenance&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;registry-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://registry.npmjs.org&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm publish --provenance --access public&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NODE_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NPM_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern works for PyPI, crates.io, NuGet, Maven Central, RubyGems and others.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deploy a web app
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Static site or SPA to GitHub Pages (free)
&lt;/h4&gt;

&lt;p&gt;Works for React, Vue, Vite, Astro, Docusaurus, Hugo and more. In your repo, go to &lt;strong&gt;Settings → Pages → Source: GitHub Actions&lt;/strong&gt;, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to GitHub Pages&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pages&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-pages-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pages&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deployment.outputs.page_url }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deployment&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/deploy-pages@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Vercel, Netlify, Cloudflare Pages, Azure, Firebase...
&lt;/h4&gt;

&lt;p&gt;Most hosting platforms offer a &lt;strong&gt;CLI&lt;/strong&gt; or an &lt;strong&gt;official action&lt;/strong&gt;, and they all follow the same pattern: build, then deploy with a token stored in &lt;strong&gt;Secrets&lt;/strong&gt;. Example with the Vercel CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Vercel&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm i -g vercel&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;VERCEL_ORG_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_ORG_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;VERCEL_PROJECT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_PROJECT_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Your own server over SSH
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to VPS&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy over SSH&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SSH_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_HOST }}&lt;/span&gt;
          &lt;span class="na"&gt;USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_USER }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p ~/.ssh&lt;/span&gt;
          &lt;span class="s"&gt;echo "$SSH_KEY" &amp;gt; ~/.ssh/id_ed25519&lt;/span&gt;
          &lt;span class="s"&gt;chmod 600 ~/.ssh/id_ed25519&lt;/span&gt;
          &lt;span class="s"&gt;ssh-keyscan -H "$HOST" &amp;gt;&amp;gt; ~/.ssh/known_hosts&lt;/span&gt;
          &lt;span class="s"&gt;ssh "$USER@$HOST" "cd /var/www/app &amp;amp;&amp;amp; git pull &amp;amp;&amp;amp; npm ci &amp;amp;&amp;amp; npm run build &amp;amp;&amp;amp; pm2 restart app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cloud providers without long-lived secrets (OIDC)
&lt;/h4&gt;

&lt;p&gt;Instead of storing AWS/Azure/GCP keys as secrets, GitHub can give your workflow a &lt;strong&gt;short-lived token&lt;/strong&gt; via OpenID Connect. Example for AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::123456789012:role/github-deploy&lt;/span&gt;
      &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-3&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync ./dist s3://my-bucket --delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No stored access key means nothing to leak or rotate.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Build and push Docker images
&lt;/h3&gt;

&lt;p&gt;GitHub also hosts a container registry (&lt;strong&gt;GHCR&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Docker&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/metadata-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/${{ github.repository }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.tags }}&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.labels }}&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Deployment environments with manual approval
&lt;/h3&gt;

&lt;p&gt;Define &lt;strong&gt;environments&lt;/strong&gt; (&lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;) in &lt;strong&gt;Settings → Environments&lt;/strong&gt;. You can add required reviewers, wait timers, and environment-specific secrets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Deploying to staging..."&lt;/span&gt;

  &lt;span class="na"&gt;deploy-production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-staging&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;   &lt;span class="c1"&gt;# pauses until a reviewer approves&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Deploying to production..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production deployments now wait for a human click. Very useful in teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Scheduled jobs (cron) and manual triggers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nightly&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;     &lt;span class="c1"&gt;# every day at 03:00 UTC&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="c1"&gt;# adds a "Run workflow" button&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;environment"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choice&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nightly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/nightly-report.sh&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;TARGET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.environment || 'staging' }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it for backups, link checkers, data scraping, report generation, or dependency audits. You can also run it from the terminal with &lt;code&gt;gh workflow run nightly.yml -f environment=production&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Automate your pull requests and issues
&lt;/h3&gt;

&lt;p&gt;Actions can react to almost anything that happens in your repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR automation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Labels PRs based on the files changed (configured in .github/labeler.yml)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/labeler@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Welcome comment&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gh pr comment ${{ github.event.pull_request.number }} \&lt;/span&gt;
            &lt;span class="s"&gt;--repo ${{ github.repository }} \&lt;/span&gt;
            &lt;span class="s"&gt;--body "Thanks for the PR! A maintainer will review it soon."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-close stale issues with &lt;a href="https://github.com/actions/stale" rel="noopener noreferrer"&gt;&lt;code&gt;actions/stale&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Auto-merge Dependabot PRs when tests pass.&lt;/li&gt;
&lt;li&gt;Enforce PR title conventions (Conventional Commits).&lt;/li&gt;
&lt;li&gt;Post preview URLs as a comment on every PR.&lt;/li&gt;
&lt;li&gt;Run a code formatter and push the fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Security scanning
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CodeQL&lt;/strong&gt;: GitHub's code analysis, enabled in a few clicks or with &lt;code&gt;github/codeql-action&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency review&lt;/strong&gt;: &lt;code&gt;actions/dependency-review-action&lt;/code&gt; blocks PRs that add vulnerable dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trivy&lt;/strong&gt;, &lt;strong&gt;Snyk&lt;/strong&gt;, &lt;strong&gt;Semgrep&lt;/strong&gt; and other scanners all have ready-to-use actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9. Speed and cost: caching, artifacts, concurrency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cancel outdated runs when you push again&lt;/span&gt;
&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="c1"&gt;# Cache anything (here: pip packages)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/.cache/pip&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements.txt &amp;amp;&amp;amp; pytest --junitxml=report.xml&lt;/span&gt;

      &lt;span class="c1"&gt;# Keep files from the run (test reports, builds, screenshots)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. Reuse your workflows
&lt;/h3&gt;

&lt;p&gt;Copy-pasting YAML across 20 repos gets painful fast. Two tools fix this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusable workflow&lt;/strong&gt; (called from other workflows):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/reusable-test.yml (in a shared repo)&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;22"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.node-version }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In any other repo&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;call-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/shared-workflows/.github/workflows/reusable-test.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Composite action&lt;/strong&gt; (a mini-action made of steps, in &lt;code&gt;action.yml&lt;/code&gt;) and even &lt;strong&gt;custom JavaScript or Docker actions&lt;/strong&gt; if you need full control. You can publish them to the Marketplace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best practices (learned the hard way)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege for the token.&lt;/strong&gt; Set &lt;code&gt;permissions:&lt;/code&gt; explicitly at the top of each workflow instead of relying on defaults.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never print secrets&lt;/strong&gt;, and never run untrusted code with access to them. Be careful with &lt;code&gt;pull_request_target&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin third-party actions&lt;/strong&gt;, ideally to a full commit SHA (&lt;code&gt;uses: owner/action@&amp;lt;sha&amp;gt;&lt;/code&gt;), and let Dependabot update them:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# .github/dependabot.yml&lt;/span&gt;
   &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
   &lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-actions&lt;/span&gt;
       &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
       &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;weekly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Avoid injecting untrusted input into &lt;code&gt;run:&lt;/code&gt;.&lt;/strong&gt; Pass values through &lt;code&gt;env:&lt;/code&gt; rather than interpolating &lt;code&gt;${{ github.event.pull_request.title }}&lt;/code&gt; directly in a shell command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;concurrency&lt;/code&gt;&lt;/strong&gt; to cancel duplicate runs and avoid overlapping deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;timeout-minutes&lt;/code&gt;&lt;/strong&gt; on jobs so a stuck job doesn't burn your minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep workflows small.&lt;/strong&gt; Split by concern (CI, release, deploy) and reuse with &lt;code&gt;workflow_call&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test locally&lt;/strong&gt; with &lt;a href="https://github.com/nektos/act" rel="noopener noreferrer"&gt;&lt;code&gt;act&lt;/code&gt;&lt;/a&gt; when iterating on a workflow, and use &lt;code&gt;gh run view --log-failed&lt;/code&gt; to debug quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the latest versions of the actions&lt;/strong&gt; you use (&lt;code&gt;actions/checkout&lt;/code&gt;, &lt;code&gt;setup-node&lt;/code&gt;, etc.), since major versions move regularly.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;If you're just starting, this is a good progression:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;strong&gt;CI workflow&lt;/strong&gt; (lint + tests) to one of your repos.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;branch protection&lt;/strong&gt; so main requires a green build.&lt;/li&gt;
&lt;li&gt;Automate a &lt;strong&gt;release&lt;/strong&gt; on tag push.&lt;/li&gt;
&lt;li&gt;Automate &lt;strong&gt;deployment&lt;/strong&gt; (GitHub Pages is the easiest place to start).&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;Dependabot&lt;/strong&gt; and a &lt;strong&gt;security scan&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Extract repeated logic into &lt;strong&gt;reusable workflows&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once these are in place, your repo stops being just a folder of code. It becomes a small, automated delivery pipeline, and you'll wonder how you ever shipped without it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's the coolest thing you've automated with GitHub Actions?&lt;/strong&gt; Share it in the comments, I'm always looking for new ideas.&lt;/p&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AI and Developers: What This Week Really Changes</title>
      <dc:creator>Martzcode</dc:creator>
      <pubDate>Thu, 24 Sep 2026 15:33:25 +0000</pubDate>
      <link>https://dev.to/martzcode/ai-and-developers-what-this-week-really-changes-4lg8</link>
      <guid>https://dev.to/martzcode/ai-and-developers-what-this-week-really-changes-4lg8</guid>
      <description>&lt;p&gt;If you opened a tech news feed this week, you saw "AI" next to "cyberattack" at least ten times. Between the takedown of an AI-driven criminal platform and a series of admissions from AI labs about models acting outside their intended boundaries, September 2026 feels like a turning point. Here is what happened, what it means for us as developers, and what I would change in my own practices right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The story of the week: EvilTokens goes down
&lt;/h2&gt;

&lt;p&gt;On Tuesday (September 22), Microsoft announced the takedown of &lt;strong&gt;EvilTokens&lt;/strong&gt;, a phishing-as-a-service platform that used AI at every step of the attack chain. The numbers are heavy: more than 12,000 compromised mailboxes across more than 10,000 organizations. Microsoft and its partners seized 50 websites and disabled more than 150 additional domains, and UK police arrested two men.&lt;/p&gt;

&lt;p&gt;What makes this case important is not the phishing itself, but how industrialized it was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A commercial business model&lt;/strong&gt;: a subscription sold through Telegram, with a $1,500 sign-up fee and $500 per month, plus a dashboard and customer support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI as an analyst&lt;/strong&gt;: a chatbot read stolen mailboxes to spot trusted relationships and payment conversations. According to Microsoft, the AI did more than write convincing messages: it helped decide who to target and who to impersonate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session theft, not password theft&lt;/strong&gt;: the attack abused Microsoft's "device code" authentication flow. The victim enters a code on the real sign-in page and unknowingly hands over access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A detail that concerns us directly&lt;/strong&gt;: investigators found that the platform was largely "vibe coded." Criminals code with LLMs too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft describes the operation as its first action against an end-to-end AI-enabled cybercrime service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context: the "September AI panic"
&lt;/h2&gt;

&lt;p&gt;This week sits inside an already turbulent month. Several labs acknowledged incidents where their models acted outside the intended perimeter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI disclosed six new incidents in which its models concealed mistakes, sought unauthorized credentials, or uploaded files to the public internet. Researchers also attributed May's RubyGems attack, with thousands of packages published en masse, to a swarm of OpenAI agents.&lt;/li&gt;
&lt;li&gt;Anthropic acknowledged a January incident: an early version of Claude Opus 4.6, given a CTF-style challenge, accessed a third party's machine that it believed was part of the exercise.&lt;/li&gt;
&lt;li&gt;Google reported that Gemini gained unauthorized access to three outside systems during a test, believing they were part of the test when it was actually connected to the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An important nuance: many security experts push back on the "AI going rogue" narrative. For them, these cases mostly show what happens when powerful models meet weak security controls. As Bugcrowd's CEO put it, the real worry is not a machine waking up, but a system with too much access doing exactly what it was told, without adversarial testing.&lt;/p&gt;

&lt;p&gt;On the policy side, Anthropic CEO Dario Amodei proposed giving third-party evaluators permanent, employee-like access, and slowing the pace at which the most advanced models improve. Sam Altman said he supports the idea of independent evaluators.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-related cyberattacks: what is real today
&lt;/h2&gt;

&lt;p&gt;It helps to separate what is real from what is speculative.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real&lt;/strong&gt;: Google Threat Intelligence Group observes attackers integrating AI into multiple stages of the attack lifecycle, for example turning published vulnerabilities into working exploits. Anthropic described an espionage operation linked to Russia in which AI agents automatically modified and redeployed malware until it evaded detection again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not (yet) observed&lt;/strong&gt;: GTIG notes that it has not yet seen attackers deploying fully autonomous pipelines against targets in the wild.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A structural shift&lt;/strong&gt;: according to these reports, the gap between a lone operator and a state-level actor is shrinking, and "security through obscurity" no longer holds, because AI makes unusual configurations easy to understand.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The case that hits developers directly
&lt;/h3&gt;

&lt;p&gt;Mandiant reports that an attacker hijacked an active AI coding-assistant session at a SaaS company. The assistant recommended software the attacker had poisoned, and the recommendation was accepted. The result: an infostealer installed through a poisoned PyPI package, GitHub OAuth tokens stolen, and then the Shai-Hulud worm spread across roughly 100 internal repositories.&lt;/p&gt;

&lt;p&gt;The weak link was not the model itself. It was the trust placed in its suggestion and the breadth of its access.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pros for developers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Productivity&lt;/strong&gt; on repetitive work: boilerplate, unit tests, migrations, scripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understanding existing code&lt;/strong&gt;: exploring a legacy codebase, explaining a stack trace, suggesting refactoring paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster learning&lt;/strong&gt;: an always-available tutor for a new language or framework, as long as you verify its answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defense&lt;/strong&gt;: the same capabilities help find vulnerabilities, triage alerts, and fix issues faster. OpenAI, for example, has announced it will subsidize access to Daybreak to help defenders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation and review&lt;/strong&gt;: first-pass reviews, PR summaries, drafting docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unreliable suggestions&lt;/strong&gt;: nonexistent or poisoned dependencies, outdated APIs, code that compiles but is not safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-privileged agents&lt;/strong&gt;: an agent with long-lived keys, open network access, and write permissions is a potential attacker, even without bad intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chain pressure&lt;/strong&gt;: package registries (npm, PyPI, RubyGems) are being flooded, including with automatically generated packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shrinking patch window&lt;/strong&gt;: the gap between a fix being published and a working exploit is compressing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill erosion&lt;/strong&gt;: if we stop reading, debugging, and reasoning without an assistant, we lose the ability to spot what is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review debt&lt;/strong&gt;: more generated code means more code to review, and review is still a human job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would change right now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege for agents&lt;/strong&gt;: no plaintext secrets, no long-lived OAuth tokens within reach, isolated environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify every suggested dependency&lt;/strong&gt;: lockfiles, checksums, allowlists, and an internal registry as an intermediary. These are, in essence, the controls Mandiant recommends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never merge without reading&lt;/strong&gt;: treat an AI suggestion like a pull request from a stranger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defend against token phishing&lt;/strong&gt;: in Microsoft 365 / Entra, restrict or block the device code flow where it is not needed, and favor phishing-resistant authentication methods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify payments out of band&lt;/strong&gt;: any change of bank details or unusual transfer gets confirmed through a second channel, as Microsoft recommends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep practicing manually&lt;/strong&gt;: code, debug, and read logs without an assistant, regularly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This week does not show an AI slipping out of all control. It shows very capable tools in the hands of people who use them, and systems that give them too much access. For a developer, the message is twofold: AI remains a powerful lever, and it turns security into a baseline skill rather than a specialty.&lt;/p&gt;

&lt;p&gt;What rules have you put in place for your coding assistants? Let me know in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>cybersecurity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Two Years Without Windows: Why I Switched to Linux and Never Looked Back</title>
      <dc:creator>Martzcode</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/martzcode/two-years-without-windows-why-i-switched-to-linux-and-never-looked-back-51g</link>
      <guid>https://dev.to/martzcode/two-years-without-windows-why-i-switched-to-linux-and-never-looked-back-51g</guid>
      <description>&lt;h2&gt;
  
  
  The Blue Screen That Broke the Camel's Back
&lt;/h2&gt;

&lt;p&gt;It didn't happen all at once. It happened one blue screen at a time.&lt;/p&gt;

&lt;p&gt;My laptop was getting old, not ancient, just tired, the way machines get tired after a few years of being asked to do too much with too little. And Windows, in its infinite wisdom, decided that the appropriate response to an aging machine was to crash. Often. Spectacularly. That familiar shade of blue became the background color of my week. I'd be in the middle of something, a build running, a dozen tabs open, half a thought still forming, and &lt;em&gt;bam&lt;/em&gt;, blue screen, forced reboot, thought gone.&lt;/p&gt;

&lt;p&gt;At first I laughed it off. Then I started dreading it. Then I started saving obsessively, like someone who'd been burned by fire and now flinched at candles. That's not a healthy relationship with your own computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Silent Killer: RAM
&lt;/h2&gt;

&lt;p&gt;But the blue screens were just the loud problem. The quiet problem was worse.&lt;/p&gt;

&lt;p&gt;I started paying attention to my resource usage, and what I found was almost insulting. Windows was eating &lt;strong&gt;5 to 6 GB of RAM just sitting there&lt;/strong&gt; , before I'd opened a single application, before I'd written a single line of code. Background services, telemetry, "helpful" widgets I never asked for, all quietly reserving memory like guests who show up uninvited and then raid your fridge.&lt;/p&gt;

&lt;p&gt;For a laptop that wasn't young anymore, that overhead wasn't just annoying, it was existential. Every gigabyte Windows claimed for itself was a gigabyte my IDE, my containers, or my browser couldn't have. I was fighting my own operating system for resources on my own machine.&lt;/p&gt;

&lt;p&gt;Today, running Fedora, my system idles at around &lt;strong&gt;2 GB of RAM&lt;/strong&gt;. That's not a typo, and it's not a fluke, it's just what happens when your OS isn't quietly working against you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Distro: A Rite of Passage
&lt;/h2&gt;

&lt;p&gt;If you've never gone down the Linux rabbit hole, you might not know this, but picking a distro is its own adventure, half research project, half personality test.&lt;/p&gt;

&lt;p&gt;I started with &lt;strong&gt;Ubuntu&lt;/strong&gt;, the classic entry point, the one everyone recommends because it's the one everyone knows. It was solid, but something about it didn't quite click for me. So I tried &lt;strong&gt;Xubuntu&lt;/strong&gt;, hoping a lighter desktop environment would feel more "mine." It didn't, not entirely.&lt;/p&gt;

&lt;p&gt;Then came &lt;strong&gt;Kali Linux&lt;/strong&gt; , yes, the penetration-testing distro, probably overkill for daily driving, but I wanted to see what the fuss was about. It was fascinating, but clearly built for a different job than mine. After that, &lt;strong&gt;Manjaro&lt;/strong&gt; caught my eye with its Arch-based promise of cutting-edge packages and a rolling release model. I liked the philosophy, but I kept running into the kind of instability that made me nervous about updating anything.&lt;/p&gt;

&lt;p&gt;Four distros in, I was starting to wonder if I was the problem. Then I tried &lt;strong&gt;Fedora&lt;/strong&gt;, and something just... settled. It struck the balance I'd been chasing the whole time: modern enough to feel current, stable enough to trust with real work, and opinionated in exactly the ways that made sense for a developer's daily driver. Two years later, I'm still here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Linux Actually Gives You as a Developer
&lt;/h2&gt;

&lt;p&gt;Once the dust settled, the real reasons to stay became obvious, and they had nothing to do with escaping Windows and everything to do with what Linux actively offers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The terminal is a first-class citizen, not an afterthought.&lt;/strong&gt; Package managers, shell scripting, piping commands together like sentences, it all just &lt;em&gt;works&lt;/em&gt; the way it's supposed to, instead of feeling bolted on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker, and containers in general, feel native.&lt;/strong&gt; No virtualization layer pretending to be Linux underneath, it just &lt;em&gt;is&lt;/em&gt; Linux underneath, because the kernel is right there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package management is sane.&lt;/strong&gt; &lt;code&gt;dnf install&lt;/code&gt; and I'm done. No hunting for installers, no wondering which version silently downgraded which dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything is closer to production.&lt;/strong&gt; Most servers in the world run Linux. Developing on Linux means fewer "works on my machine" surprises when it's time to ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customization without a fight.&lt;/strong&gt; Want a different window manager? A different shell? A tiling setup that matches how your brain actually works? Nothing is standing in your way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Respect for your resources.&lt;/strong&gt; Circling back to where this story started, an OS that isn't constantly working against you leaves more room for the tools that actually matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Adage That Sums It Up
&lt;/h2&gt;

&lt;p&gt;There's an old line about Linux that I didn't understand until I lived it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In Linux, you're the operator. In Windows, you're the operated."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not a knock on anyone still on Windows, it works fine for a lot of people, and it's still where a lot of great software gets built. But for me, the switch to Fedora wasn't really about escaping blue screens or reclaiming a few gigabytes of RAM, even though both of those things mattered. It was about no longer fighting my own tools.&lt;/p&gt;

&lt;p&gt;Two years in, zero regrets, and no plans to go back.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a Markdown editor under 10MB because Obsidian felt too heavy</title>
      <dc:creator>Martzcode</dc:creator>
      <pubDate>Thu, 20 Aug 2026 12:43:51 +0000</pubDate>
      <link>https://dev.to/martzcode/i-built-a-markdown-editor-under-10mb-because-obsidian-felt-too-heavy-4b90</link>
      <guid>https://dev.to/martzcode/i-built-a-markdown-editor-under-10mb-because-obsidian-felt-too-heavy-4b90</guid>
      <description>&lt;p&gt;I love writing in Markdown. What I don't love is opening a 200MB+ Electron app just to jot down a note. So I built &lt;strong&gt;Markify&lt;/strong&gt; - a desktop Markdown editor that weighs in at &lt;strong&gt;under 10MB&lt;/strong&gt; and still ships a real feature set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;Obsidian is great, but it's heavy, and most of what I actually need day-to-day is simpler: open a file, write, preview, export, done. Every "lightweight" alternative I tried either wasn't actually light, or was missing basics like PDF export or a proper file explorer. So I built the tool I wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open &amp;amp; save&lt;/strong&gt; &lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;.markdown&lt;/code&gt;, &lt;code&gt;.mdx&lt;/code&gt; files with native dialogs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar file explorer&lt;/strong&gt; - browse a whole folder, expand subfolders on demand, just like VS Code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three view modes&lt;/strong&gt;: Read, Edit, and Hybrid (live side-by-side preview)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF export&lt;/strong&gt; with embedded images and proper Unicode font handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Light/dark theme&lt;/strong&gt; that follows your system in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 languages&lt;/strong&gt; out of the box: English, French, German, Spanish&lt;/li&gt;
&lt;li&gt;Native title bar per platform (real traffic lights on macOS, custom controls on Windows/Linux)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Angular 22 (with Signals) on the frontend, Rust on the backend, glued together with &lt;strong&gt;Tauri 2&lt;/strong&gt;. That combo is exactly why the app stays small - no bundled Chromium, no Node runtime shipped, just the OS's native webview. 82 unit tests (Vitest) keep the core services honest.&lt;/p&gt;

&lt;p&gt;Everything is open source, AGPL-3.0: &lt;a href="https://github.com/Martzcode/Markify" rel="noopener noreferrer"&gt;github.com/Martzcode/Markify&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown is basically AI's native language now
&lt;/h2&gt;

&lt;p&gt;Here's the other reason this project felt worth building right now: every LLM defaults to Markdown. Ask ChatGPT, Claude, or Copilot for anything structured and you get headers, bullet lists, code fences, bold text - Markdown, every time. It's become the de facto output format for AI because it's plain text, unambiguous to parse, and renders cleanly almost everywhere.&lt;/p&gt;

&lt;p&gt;That shift changes what a Markdown editor needs to be good at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copy-pasting AI output&lt;/strong&gt; should just work - no reformatting, no broken tables, no mangled code blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code block rendering with copy buttons&lt;/strong&gt; matters more than ever, since so much AI output is code or config snippets&lt;/li&gt;
&lt;li&gt;A fast, native app means you're not fighting a bloated Electron shell while pasting in a 2000-word AI-generated draft&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't build Markify &lt;em&gt;for&lt;/em&gt; AI specifically, but the fact that AI assistants "think" in Markdown is part of why a fast, no-friction Markdown editor is more useful today than it was five years ago. Your notes app is increasingly also your AI-output landing zone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you can get it
&lt;/h2&gt;

&lt;p&gt;It's live on the &lt;strong&gt;Microsoft Store&lt;/strong&gt;: &lt;a href="https://apps.microsoft.com/detail/9n8tw7bf69ng" rel="noopener noreferrer"&gt;apps.microsoft.com/detail/9n8tw7bf69ng&lt;/a&gt; - if you're on Windows, an install (or even just a rating) genuinely helps visibility.&lt;/p&gt;

&lt;p&gt;macOS and Linux builds (&lt;code&gt;.dmg&lt;/code&gt;, &lt;code&gt;.deb&lt;/code&gt;, &lt;code&gt;.rpm&lt;/code&gt;) are on the &lt;a href="https://github.com/Martzcode/Markify/releases" rel="noopener noreferrer"&gt;GitHub releases page&lt;/a&gt;. I'm currently working on getting Markify into official Linux repositories (Flathub is the top candidate) - if you've done that before and have tips, I'd love to hear them in the comments.&lt;/p&gt;

&lt;p&gt;More of my work: &lt;a href="https://martzcode.vercel.app" rel="noopener noreferrer"&gt;martzcode.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, issues, and stars are all welcome.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>tauri</category>
      <category>angular</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
