<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged githubactions</title>
<link>https://coderlegion.com/tag/githubactions</link>
<description>Powered by Question2Answer</description>
<item>
<title>Dynamic GitHub Actions Dropdowns — A Clever Workaround Made Reusable!</title>
<link>https://coderlegion.com/31452/dynamic-github-actions-dropdowns-a-clever-workaround-made-reusable</link>
<description>&lt;p&gt;GitHub Actions doesn't natively support dynamically populated dropdown options for &lt;code&gt;workflow_dispatch&lt;/code&gt; inputs. This can become a maintenance headache, especially when managing multiple packages in an npm monorepo.&lt;/p&gt;
&lt;p&gt;I came across a clever workaround shared by &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jeevan-vj&quot;&gt;@jeevan-vj&lt;/a&gt;, demonstrating how to dynamically maintain dropdown options by automatically updating GitHub Actions workflow YAML files.&lt;/p&gt;
&lt;p&gt;Inspired by that approach, I built a &lt;strong&gt;reusable GitHub Actions workflow&lt;/strong&gt; that uses a dedicated &lt;code&gt;WORKFLOW_TOKEN&lt;/code&gt; to update workflow files and automatically discover publishable packages in an npm monorepo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here's what my implementation adds:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Automatic package discovery&lt;/strong&gt; — Detects publishable npm workspace packages without maintaining a hardcoded list.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Multi-workflow synchronization&lt;/strong&gt; — Keeps package dropdown options consistent across multiple GitHub Actions workflows.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;YAML formatting preservation&lt;/strong&gt; — Uses Python and &lt;code&gt;ruamel.yaml&lt;/code&gt; to update workflow configurations while preserving their structure and formatting.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Automatic and manual execution&lt;/strong&gt; — Supports synchronization on push as well as manual triggering when needed.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Configurable token authentication&lt;/strong&gt; — Accepts a &lt;code&gt;workflow-token&lt;/code&gt; secret, allowing each consuming repository to choose its own secret name.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;No unnecessary commits&lt;/strong&gt; — Updates workflow files only when package options actually change.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;The end result?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My &lt;strong&gt;release, publish, restore, reset, and release-draft workflows&lt;/strong&gt; now automatically stay synchronized with the publishable packages in my npm monorepo.&lt;/p&gt;
&lt;p&gt;No more manually updating package dropdowns every time a package is added, renamed, or removed.&lt;/p&gt;
&lt;p&gt;Even better, the solution is reusable across repositories, so I have adopted the same automation in other repos without duplicating the synchronization logic.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Original inspiration — @jeevan-vj:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jeevan-vj/iamjeevan/blob/master/data/blog/dynamically-populating-parameter-options-in-manual-github-action.md&quot;&gt;https://github.com/jeevan-vj/iamjeevan/blob/master/data/blog/dynamically-populating-parameter-options-in-manual-github-action.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;My reusable workflow implementation:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/moyarich/reusable-workflows/blob/main/.github/workflows/reusable_sync-release-package-choices.yml&quot;&gt;https://github.com/moyarich/reusable-workflows/blob/main/.github/workflows/reusable_sync-release-package-choices.yml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Real-world implementation — Workspace Tools:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/moyarich/workspace-tools/blob/main/.github/workflows/sync-release-package-choices.yml&quot;&gt;https://github.com/moyarich/workspace-tools/blob/main/.github/workflows/sync-release-package-choices.yml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Huge credit to @jeevan-vj&lt;/strong&gt; for sharing the original workaround. It's a great example of how an open-source idea can evolve into a reusable solution that benefits other developers.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;How are you managing dynamic inputs in your GitHub Actions workflows?&lt;/strong&gt; Have you found another approach?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31452/dynamic-github-actions-dropdowns-a-clever-workaround-made-reusable</guid>
<pubDate>Fri, 16 Oct 2026 19:57:00 +0000</pubDate>
</item>
<item>
<title>My First Project: From a Local Folder to GitHub Using Git and SSH</title>
<link>https://coderlegion.com/30212/my-first-project-from-a-local-folder-to-github-using-git-and-ssh</link>
<description>&lt;h2 id=&quot;my-first-project-from-a-local-folder-to-git-hub-using-git-and-ssh&quot;&gt;My First Project: From a Local Folder to Git hub Using Git and SSH&lt;/h2&gt;
&lt;h3 id=&quot;git&quot;&gt;GIT&lt;/h3&gt;
&lt;h4 id=&quot;introduction&quot;&gt;Introduction&lt;/h4&gt;
&lt;p&gt;Git is an open source version control system used to manage projects. It is fast and efficient to manage projects using git. You can easily track the project version, history, modifications, and many more project activities. It allows for ease of collaboration while working on projects as teams.&lt;/p&gt;
&lt;h4 id=&quot;installing-git&quot;&gt;Installing Git&lt;/h4&gt;
&lt;p&gt;Download and install git from &lt;a rel=&quot;nofollow&quot; href=&quot;https://git-scm.com/install/windows&quot;&gt;Git&lt;/a&gt;. Ensure you download for your respective operating system.&lt;/p&gt;
&lt;h4 id=&quot;configuring-git&quot;&gt;Configuring Git&lt;/h4&gt;
&lt;p&gt;Before using Git effectively, you have to configure your identity by setting up your &lt;em&gt;username&lt;/em&gt; and &lt;em&gt;email&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This allows to easily track any changes to your project and the associate, that is; who made the changes?&lt;/p&gt;
&lt;p&gt;To set up your identity, follow the following steps, using Git Bash a git terminal:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Steps&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To set your username&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git config --global user.name &quot;YOUR NAME&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/d3wqbnuhko7gwa79qq0z.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;In place of &lt;em&gt;YOUR NAME&lt;/em&gt; type in your correct user name that will be associated with your github account activity.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To set your email&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git config --global user.email &quot;youemail@example.com&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ajn068mbc9hco8giv5ql.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; It is recommended to use your correct email that is associated with your GitHub account.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Rename you GitHub branch to &lt;em&gt;main&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git config --global init.Branch main&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/zqxh5s6g0u5xabnqjrgm.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After setting up your identity you can verify your configurations to confirm your identity for each field set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To check your:&lt;/p&gt;
&lt;p&gt;User name&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`git config --global user.name`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Email&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`git config --global user.email`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Branch&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global init.Branch`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;OR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can use the following command to check all you account details instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`git config --global --lists`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/e2izfyzfr0rd5sqmr6sg.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;ssh&quot;&gt;SSH&lt;/h3&gt;
&lt;p&gt;SSH key is a secure credential used to authentication to accessing network resources confidentially without need of a password. The ssh-key contains two keys. A &lt;em&gt;&lt;strong&gt;Public&lt;/strong&gt;&lt;/em&gt; and &lt;em&gt;&lt;strong&gt;Private&lt;/strong&gt;&lt;/em&gt; key. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public key&lt;/strong&gt; is used publicly, that is it is shared between peers. Used to identify you remote resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Private key&lt;/strong&gt; it is a pair key that is kept as a secret for identifying your local machine.&lt;/p&gt;
&lt;h4 id=&quot;generating-an-ssh-key&quot;&gt;Generating an SSH Key&lt;/h4&gt;
&lt;p&gt;Using Git Bash&lt;/p&gt;
&lt;p&gt;Before generating a new SSH Key, it is recommended to check and confirm if you have an existing key which you can use.&lt;/p&gt;
&lt;p&gt;To check if you have an existing key use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`ls -al ~/.ssh`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command will list all existing ssh keys. Here sample output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rfnrwqgtgllt5u087bbh.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;If there is no existing key, you can generate a new key that you will use to link your computer with your github account.&lt;/p&gt;
&lt;p&gt;To generate a new ssh key, follow the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Genereate a new ssh-key&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh-keygen -t ed25529 -C &quot;youremail@example.com&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The email should be the one associated with with your github account.&lt;/p&gt;
&lt;p&gt;Follow the prompt by adding the name of your key, and also you can add the passphrase which is optional.&lt;/p&gt;
&lt;p&gt;The passhrase should be memorable, you will need to use it each time you want to access you github account.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add your key to the SSH-Agent.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Open your Powershell Terminal and follow the steps below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- Run the following command to start your SSH Agent

	`Get-Service -Name ssh-agent | Set-Service StartupType Manual`

- Add your key to the  path using the  command

	`ssh-add &amp;lt;PATH/TO/YOUR/PRIVATE_KEY&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the command, you should specifify the path to your private key and add it to your host machine's ssh-agent. i.e. &lt;code&gt;ssh-add &quot;C:/Users/&amp;lt;YOUR_USER_NAME/.ssh/&amp;lt;YOUR_PRIVATE_KEY&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you don't know your computer's user name, you can use the &lt;code&gt;whoami&lt;/code&gt; command to see your username.&lt;/p&gt;
&lt;p&gt;Your _PRIVATE_KEY&lt;em&gt;NAME&lt;/em&gt; is the name that you gave to your key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- Once you're done, close your Powershell terminal and return to Git Bash.

- Type the following command

	`git config --global core.sshCommand &quot;C:/Windows/System32/OpenSSH/ssh.exe`

- Then Clip and copy your key using the following commands:

	`clip &amp;lt; ~/.ssh/&amp;lt;PUBLIC_KEY.pub&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;i.e &lt;code&gt;clip &amp;lt; /.ssh/my_key.pub&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`cat ~/.ssh/&amp;lt;PUBLIC_KEY.pub&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;i.e. 'cat ~/.ssh/my_key.pub` to view your public ssh key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- After displaying your key, select and copy the key to add to your GitHub account.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;adding-your-key-to-github&quot;&gt;Adding Your Key to GitHub&lt;/h4&gt;
&lt;p&gt;To connect your computer and git-hub is necessary to facilitate access to your remote resources from your local computer.&lt;/p&gt;
&lt;p&gt;You need to add your SSH Keys to GitHub to facilitate the connection. To achieve this, follow the steps:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you have an existing GitHub account:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;a. Login to your account using your email/username and password.&lt;/p&gt;
&lt;p&gt;b. From the profile section, go to &lt;em&gt;Settings&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;c. Scroll to &lt;em&gt;SSH and GPG Key&lt;/em&gt;. Navigate and click on &lt;em&gt;New SSH Key&lt;/em&gt; on the SSH Key Section to your right.&lt;/p&gt;
&lt;p&gt;e. Add the &lt;em&gt;Title/Name&lt;/em&gt; of the key i.e. &lt;em&gt;Ubuntu Laptop&lt;/em&gt;, the key will be used for &lt;em&gt;Authentication&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;f. Paste your key to the available textbox.&lt;/p&gt;
&lt;p&gt;g. Finally, click on &lt;em&gt;Add Key&lt;/em&gt; to save your key. You'll be prompted to re-enter your git-hub account password (login password), to confirm your authenticity before saving.&lt;/p&gt;
&lt;p&gt;Now you are ready to go. Before you start, it is good you test your connection to check if it was successful.&lt;/p&gt;
&lt;p&gt;Run Powershell as an Administrator and use the following command to test connection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`ssh -T git@github.com`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the connection is successful, your username will be displayed along with a success message.&lt;/p&gt;
&lt;p&gt;If successful, you are good to go and work on your project.&lt;/p&gt;
&lt;h3 id=&quot;working-with-folders&quot;&gt;Working With Folders&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;To keep you project structural and more organized; you've to keep its files and scripts in folders.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will make it easier to navigate and access the project. Follow the steps below to work with folders in Windows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In Git Bash Terminal(Opened normally)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To navigate and change location use the following command&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`cd &amp;lt;path_location&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To select the current location&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`cd .`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get to the previous location one step back use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`cd ..`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Getting back to the root directory use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`cd ~`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also move to a specific folder by specifying the path to that folder&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`cd &amp;lt;path/to/folder&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;example: &lt;code&gt;cd &quot;C:/Users/Admin/Desktop/My-Project&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My-Project/&lt;/em&gt; is a folder located in Desktop, which is for the user with the username &lt;em&gt;Admin&lt;/em&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To create a directory or folder use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`mkdir &amp;lt;directory_name&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;i.e. Create a directory named &lt;em&gt;My-First-Project&lt;/em&gt; in Desktop &lt;code&gt;mkdir My-First-Project&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Navigate to your created folder. Assuming you are on Desktop, use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`cd My-First-Project/`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So your bash terminal path will look like &lt;code&gt;~/Desktop/My-First-Project&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To list and view items use the following command&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`ls`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It will list all files and folders excluding the hidden files and folders. As shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/yodsemd3r03fx0ltw8lk.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;To list and view all files and folders including the hidden ones you add &lt;em&gt;-a&lt;/em&gt; attribute to the &lt;code&gt;ls&lt;/code&gt; command as below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`ls -a`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/m0ik6vtw5w4ymbjcdzdo.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;To list files and display their attributes we add the &lt;em&gt;-l&lt;/em&gt; attribute to the &lt;code&gt;ls&lt;/code&gt; command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`ls -l`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/g98xshsqzai093amcfeu.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; You can combine the attributes to list all files and their attributes i.e. as &lt;code&gt;ls -al&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can delete an empty folder using &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`rm -r &amp;lt;folder_name&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To delete a folder together with its contents we use the &lt;code&gt;rm -r &amp;lt;non-empty_folder_name&amp;gt;&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Example: Create a folder in you my project folder using the &lt;code&gt;mkdir&lt;/code&gt; command, inspect the folder created and its contents, then delete the folder using the rm command:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Steps&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create scripts: &lt;code&gt;mkdir scripts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to scripts and back to your project folder: &lt;code&gt;cd scripts/&lt;/code&gt; and &lt;code&gt;cd ..&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inspect scripts: &lt;code&gt;ls scripts/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete scripts: &lt;code&gt;rm -r scripts/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/02201e2p7xh3f724jq9d.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; &lt;code&gt;rm&lt;/code&gt; without any attribute will no delete a directory/folder even if empty, the attribute &lt;em&gt;-r&lt;/em&gt; tells the command &lt;code&gt;rm&lt;/code&gt; that what you're deleting is a directory.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can copy and paste contents between different folders.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To copy contents moves a duplicate of the files to another location leaving the original intact.&lt;/p&gt;
&lt;p&gt;While using the &lt;code&gt;cp&lt;/code&gt; command you have to specify the file/contents path and the destination address. Example:&lt;/p&gt;
&lt;p&gt;To copy some file named hello.txt to the scripts folder; you need to recreate the folder and create the file in the root directory then copy it to scripts folder in your projects folder&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Steps&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In your project folder create scripts &lt;code&gt;mkdir scripts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to your project root folder and create the file &lt;em&gt;hello.txt&lt;/em&gt; &lt;code&gt;touch hello.txt&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the file to scripts &lt;code&gt;cp hello.txt ~/scripts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inspect both locations to see that the file exists in both location. &lt;code&gt;ls&lt;/code&gt; and &lt;code&gt;ls scripts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; You can specify the file path in the cp command if the file is in a different location and the destination path address &lt;/p&gt;
&lt;p&gt;i.e. &lt;code&gt;cp &amp;lt;~/path/to/file_to_be_copied.txt&amp;gt; &amp;lt;~/path/to_destination/&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can move a file or any content completely from the source to an new destination using the &lt;code&gt;mv&lt;/code&gt; command.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example: Move &lt;em&gt;hello.txt&lt;/em&gt; file from the project's folder to a new folder named &lt;em&gt;docs&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`mv hello.txt docs/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; You can also specify the file path in case the files and destination address are in different locations i.e. &lt;code&gt;mv &amp;lt;~/path/from_source/hello.txt&amp;gt; &amp;lt;~/path/to_destination_folder/&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;working-with-files&quot;&gt;Working With Files&lt;/h3&gt;
&lt;p&gt;While working in your project you will need to create different files for your project.&lt;/p&gt;
&lt;p&gt;The different files will be saved in different directories depending on their type and use. &lt;/p&gt;
&lt;p&gt;The organization will help us to achieve a proper project structure, easier to navigate and access our project files.&lt;/p&gt;
&lt;p&gt;We will create our files using Git Bash terminal.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To create a file we'll use the &lt;code&gt;touch&lt;/code&gt; command. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a file named &lt;em&gt;hello-world.txt&lt;/em&gt; in your project root directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`touch hello-world.txt`
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;code&gt;ls&lt;/code&gt; command to confirm your file has been created.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To view the contents of the file we use the &lt;code&gt;cat&lt;/code&gt; command, since our file is empty it will display 'nothing'.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`cat hello-world.txt`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To add contents to our file we use the &lt;code&gt;echo&lt;/code&gt; command.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the following text to the file &lt;em&gt;hello-world.txt&lt;/em&gt;; &quot;Hello, World&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`echo &quot;Hello, World!&quot; &amp;gt; hello-world.txt`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uvhxthqnnamj5zwqfzo3.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;We use the &lt;em&gt;&amp;gt;&lt;/em&gt; to redirect the output to our file. &lt;em&gt;&amp;gt;&lt;/em&gt; Rewrites any content in the file. &lt;/p&gt;
&lt;p&gt;If you want to add more content to the file use &lt;em&gt;&amp;gt;&amp;gt;&lt;/em&gt;, more text will be added to our file. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/zrmqjjh6fsn44xh9hepq.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Example; add the following text to our file &quot;I am happy to learn SSH and Git&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`echo &quot;I am happy to learn Git and SSH&quot; &amp;gt;&amp;gt; hello-world.txt`
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cat hello-world.txt&lt;/code&gt; to view the contents of the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/hio4sg9opdiguztkbne7.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;em&gt; Editing you file using nano &lt;/em&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Nano is an inbuilt text editor in Git Bash.&lt;/p&gt;
&lt;p&gt;It helps us to navigate through our files; scripts, text file, .... once we make mistakes.&lt;/p&gt;
&lt;p&gt;To open our script or text file we use the &lt;code&gt;nano&lt;/code&gt; command in git bash then specify the file we want to open, for example open &lt;em&gt;hello-world.txt&lt;/em&gt; using nano:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`nano hello-world.txt'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/atsfbilrrzl8gqw8swht.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are many more commands for nano that will help you edit your text file. For example the most common commands are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. Ctr + O - To save your file

2. Ctrl + X - To exit your nano text editor

3. Ctrl + K - To Cut text in the editor

4. Ctrl + U - To Paste your text

5. Ctrl + G - To find help for you nano editor.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are many more commands of help that you can use to edit you files in nano editor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gyapzhzkcv6s1msgglo4.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;your-first-project&quot;&gt;Your First Project&lt;/h3&gt;
&lt;h4 id=&quot;initialize-your-first-project-with-git&quot;&gt;Initialize your first project with git&lt;/h4&gt;
&lt;p&gt;Before running the command, check to confirm that you are in your project folder using the &lt;code&gt;pwd&lt;/code&gt; command. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pwd&lt;/code&gt; command means &lt;em&gt;print working directory&lt;/em&gt;  it shows your current location with regard to the root directory.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vj63ecqsyjcin6wrsoct.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;If in your project directory, the path will end with the name of the project folder. I.e. &lt;em&gt;My-First-Project&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Use the &lt;code&gt;git init&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ejy9wh4olyjgdhwjcm7l.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The command is used to initialize our project directory as a git repository.&lt;/p&gt;
&lt;h4 id=&quot;git-repository-structure-&quot;&gt;Git repository structure.&lt;/h4&gt;
&lt;p&gt;After running the above command, you may notice notification  of git initializing an empty git repo...&lt;/p&gt;
&lt;p&gt;If you list to view the structure using the &lt;code&gt;ls&lt;/code&gt; command you may notice a &lt;em&gt;.git/&lt;/em&gt; folder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/17fnm7nczt2emkd8e2m2.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The folder contains Git's internal repository information, which git uses to manage activities such as: push, commits, pull, Branches, project history, repository configurations,....&lt;/p&gt;
&lt;h4 id=&quot;checking-the-project-status&quot;&gt;Checking the project status&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	`git status`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Used to show what's happening with your project currently. &lt;/p&gt;
&lt;p&gt;It can show things such as untracked files, staged files to be committed yet, if the working tree is clean meaning everything has been committed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/739uf4cr7umrhtlgwauv.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The above show that there is nothing that has been staged for commit, there are three untracked files in the initialized repository.&lt;/p&gt;
&lt;h4 id=&quot;staging-your-project-files&quot;&gt;Staging your project files&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	`git add .`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bd1enh0j8cunas7fsgq4.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;It adds your project files to git for tracking.&lt;/p&gt;
&lt;p&gt;Checking on the status of the staged file, you should get such an output with &lt;code&gt;git status&lt;/code&gt; command&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/q52vf48i6k1hglqddkj7.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;It prepares all you changes for the next commit.&lt;/p&gt;
&lt;p&gt;You can stage a single file by specifying the file/folder you want to stage i.e. &lt;code&gt;git add &amp;lt;file_name.txt&amp;gt;&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/omjr8oo506a1e1ulotbf.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;You should see almost a similar output when using &lt;code&gt;git status&lt;/code&gt; command after staging a single file&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/h2jalyli8gb854gc8mm2.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using the &lt;em&gt;.&lt;/em&gt; means you stage everything in your project folder.&lt;/p&gt;
&lt;h4 id=&quot;commiting-you-staged-files&quot;&gt;Commiting you staged files&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	`git commit -m &quot;&amp;lt;commit message&amp;gt;&quot;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creates a saved checkpoint.&lt;/p&gt;
&lt;p&gt;The message should be precise and clear, related to the activity carried out. i.e. &quot;My First Project Using Git Commit&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/tkz6an35n2381ldspn67.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;It does not upload anything to git-hub yet.&lt;/p&gt;
&lt;h4 id=&quot;uploading-to-git-hub&quot;&gt;Uploading to git-hub&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	`git push -u origin &amp;lt;branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ijtfrh9q1669086oqqe0.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sends all the committed changes to GitHub. However, in our case we get the above error because we have not linked our local project repo to a git-hub remote repository.&lt;/p&gt;
&lt;p&gt;We will be linking our repositories in the next section.&lt;/p&gt;
&lt;p&gt;After running this command, you can easily check on the changes whether they reflect on GitHub via your browser.&lt;/p&gt;
&lt;p&gt;However, for us we are yet to see these changes. This may not work since we are yet to create a remote repository which we will then connect to our local project in our laptop.&lt;/p&gt;
&lt;h4 id=&quot;creating-a-remote-repository&quot;&gt;Creating a remote repository&lt;/h4&gt;
&lt;p&gt;To create a remote repository, follow the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Login to your git-hub account&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In your account click on &quot;Create New Repository&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter the name of your Repository as &quot;My-First-Project&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not create a README, uncheck the checkbox&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provide a brief descriptions for your project. The description is: &quot;My First Project: From a Local Folder to Git hub Using Git and SSH&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then click on Create Repository&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A new empty repository is created with the name &quot;My-First-Project&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/7rg1zy9xdj87p53dezpc.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Navigate to your repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the &lt;em&gt;code&lt;/em&gt; section, then click on &lt;em&gt;ssh&lt;/em&gt; tab and copy the &lt;em&gt;ssh code&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/dpdkinyysthajwlk94xq.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;We'll use the ssh code to connect our remote and local repositories before pushing our project.&lt;/p&gt;
&lt;h4 id=&quot;connecting-our-local-and-remote-repositories&quot;&gt;Connecting our local and remote repositories&lt;/h4&gt;
&lt;p&gt;Navigate to your git initialized  project folder&lt;/p&gt;
&lt;p&gt;Type in the following command and thereafter paste the ssh code for your remote repository and hit enter.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`git remote add origin &amp;lt;your_remote_ssh_code&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;i.e. &lt;code&gt;git remote add origin git@github.com:MakoriNyachaki/My-First-Github-Project.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/fv5763zw6bnl09wtykjt.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;rename-your-branch&quot;&gt;Rename your branch&lt;/h4&gt;
&lt;p&gt;If your remote repository's branch is not &lt;em&gt;main&lt;/em&gt; rename it to &lt;em&gt;main&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	`git branch -m main`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/d27kjda6cdi1awwjhl84.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;check-the-remote&quot;&gt;Check the remote&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	`git remote -v`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mcklkfey0gthgqyvqati.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;push-your-project-to-git-hub&quot;&gt;Push your project to git-hub&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;	`git push -u origin &amp;lt;branch_name&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;i.e. &lt;code&gt;git push -u origin main&lt;/code&gt; this works to upload our committed changes to our git-hub repository.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3isilkfwccmxlgra9kif.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;An introductory article for learning GIT and SSH. It is thrilling practical exercise for generating ssh keys, working with folders and files, connecting local project folders with remote repositories, and working with git. The introductory article requires one to do further research on so many commands that are available for the different tools we have used here in order to be an expert. Let's learn forward.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30212/my-first-project-from-a-local-folder-to-github-using-git-and-ssh</guid>
<pubDate>Thu, 08 Oct 2026 04:21:17 +0000</pubDate>
</item>
<item>
<title>AURA v0.1.0: Deterministic Trigger Extraction, Auditable Math &amp; a Self-Healing Analytics Engine</title>
<link>https://coderlegion.com/28731/aura-v0-deterministic-trigger-extraction-auditable-math-self-healing-analytics-engine</link>
<description>&lt;p&gt;If you follow AI safety, you know the uncomfortable truth: modern LLM guardrails are failing not because of complex zero-day exploits, but because of sloppy heuristics, brittle filters, and naive prompt-matching.&lt;/p&gt;
&lt;p&gt;We built &lt;strong&gt;AURA&lt;/strong&gt; to be pragmatic: deterministic, testable, fully auditable, and completely immune to the hallucination of its own telemetry.&lt;/p&gt;
&lt;p&gt;Today, we’re unpacking &lt;strong&gt;AURA v0.1.0&lt;/strong&gt; — explaining our architectural decisions, showing real code excerpts, diving into non-linear risk scoring math, and solving the silent problem of lost repository analytics on GitHub.&lt;/p&gt;
&lt;h3 id=&quot;1-system-architecture-at-a-glance&quot;&gt;1. System Architecture at a Glance&lt;/h3&gt;
&lt;p&gt;Data flow in AURA is intentionally simple, pipeline-driven, and fully reproducible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Source Cases&lt;/strong&gt; (&lt;code&gt;public_cases/*.json&lt;/code&gt;) → Cleaned, validated, and normalized via &lt;code&gt;scripts/normalize-percases.ts&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rule Extraction&lt;/strong&gt; (&lt;code&gt;scripts/extract-triggers.ts&lt;/code&gt;) → Contextual regex and sliding-window token analysis. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weights &amp;amp; Signal Mapping&lt;/strong&gt;  → Derived from &lt;code&gt;config/signal-mapping.json&lt;/code&gt; and saved to &lt;code&gt;config/trigger-weights.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoring &amp;amp; Policy Enforcement&lt;/strong&gt; (&lt;code&gt;scripts/recalc_confidence.ts&lt;/code&gt;) → Non-linear math transform + cross-check audit logic via &lt;code&gt;scripts/policy/crossCheckAdapter.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persisted Telemetry&lt;/strong&gt; → Automated daily snapshots written to &lt;code&gt;analytics/traffic-history.json&lt;/code&gt; via GitHub Actions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img width=&quot;auto&quot; height=&quot;auto&quot; alt=&quot;aura-pipeline-v3&quot; src=&quot;https://github.com/user-attachments/assets/3d816ee5-7ce2-473d-a6ab-235986cb1834&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-non-linear-risk-normalization-why-we-don-t-just-sum-things-up-&quot;&gt;2. Non-Linear Risk Normalization: Why We Don't Just &quot;Sum Things Up&quot;&lt;/h3&gt;
&lt;p&gt;We compute a transparent raw evidence sum (&lt;code&gt;confidence_raw&lt;/code&gt;) and convert it into a bounded score via a diminishing‑returns exponential:&lt;/p&gt;
&lt;p&gt;\[
\text{confidence} = 1 - e^{-\alpha \cdot \text{confidence\_raw}}
\]&lt;/p&gt;
&lt;p&gt;This keeps scores in [0...1] and avoids noisy amplification from many weak cues. Implementation (excerpt from &lt;code&gt;scripts/recalc_confidence.ts&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// computedRaw is the honest sum of trigger weights + cross-check contributions
const alpha = (typeof cfg.normAlpha === 'number') ? cfg.normAlpha : 1.0;
const normalized = 1 - Math.exp(-alpha * computedRaw);
let newVal = Math.round(normalized * 100) / 100;
if (newVal &amp;lt; minFloor) newVal = minFloor;
e.confidence_raw = Math.round(computedRaw * 100) / 100;
e.confidence = newVal;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;tuning-sensitivity-math-block-1-&quot;&gt;Tuning Sensitivity (\({\alpha}\))&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lower \(\alpha\) (0.1 - 0.2):&lt;/strong&gt; Conservative normalization; requires heavier evidence to push confidence towards 1.0.    &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Higher \({\alpha}\) (0.5+):&lt;/strong&gt; Aggressive sensitivity for high-security environments.    &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AURA Baseline (\(\alpha = 0.3\)):&lt;/strong&gt; Striking a balance where &lt;strong&gt;\(\text{confidence\_raw} = 3.0\) yields \(\approx 0.59\)&lt;/strong&gt;, requiring    compounding signals for a hard policy block.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-hardening-trigger-extraction-sliding-windows-amp-contextual-anchors&quot;&gt;3. Hardening Trigger Extraction: Sliding Windows &amp;amp; Contextual Anchors&lt;/h3&gt;
&lt;p&gt;Keyword lists are a recipe for false positives. Matching a naive string like &lt;code&gt;&quot;generate 500&quot;&lt;/code&gt; catches harmless test cases alongside malicious payloads.&lt;/p&gt;
&lt;p&gt;To balance precision and recall, AURA v0.1.0 introduces &lt;strong&gt;ordered-within-window token matching&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;sliding-window-engine&quot;&gt;Sliding Window Engine&lt;/h4&gt;
&lt;p&gt;Excerpt from &lt;code&gt;scripts/extract-triggers.ts&lt;/code&gt; showing how AURA tolerates small syntactic noise without matching scattered words across an entire prompt:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function containsOrderedWithinWindow(haystack: string[], needle: string[], window = 5): boolean {
  if (needle.length === 0) return false;
  if (needle.length === 1) return haystack.indexOf(needle[0]) !== -1;
  for (let i = 0; i &amp;lt; haystack.length; i++) {
    if (haystack[i] !== needle[0]) continue;
    let idx = i + 1;
    let matched = 1;
    for (let k = 1; k &amp;lt; needle.length &amp;amp;&amp;amp; idx &amp;lt; Math.min(haystack.length, i + window + 1); idx++) {
      if (haystack[idx] === needle[k]) { matched++; k++; }
    }
    if (matched === needle.length) return true;
  }
  return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;contextual-anchors-vs-co-occurrence-cues&quot;&gt;Contextual Anchors vs. Co-Occurrence Cues&lt;/h4&gt;
&lt;p&gt;In &lt;code&gt;config/trigger-extraction.json&lt;/code&gt;, bulk asset creation explicitly requires a deception target:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;trigger&quot;: &quot;non-consensual pattern generation&quot;,
  &quot;pattern&quot;: &quot;\\bgenerate\\s+\\d+\\s+(?:deceptive\\s+assets|phishing\\s+emails|fake\\s+documents|fake\\s+profiles|fake\\s+accounts|malicious\\s+payloads|spam\\s+emails|synthetic\\s+attacks)\\b&quot;,
  &quot;description&quot;: &quot;Bulk-generation demand with deception-specific targets.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For generic terms like &lt;code&gt;&quot;audit&quot;&lt;/code&gt;, we mandate multi-token co-occurrence:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;trigger&quot;: &quot;unauthorized audit camouflage&quot;,
  &quot;cues&quot;: [&quot;audit&quot;, &quot;unauthorized&quot;, &quot;independent&quot;, &quot;bypass&quot;, &quot;without permission&quot;],
  &quot;description&quot;: &quot;Require co-occurrence of 'audit' with authorization‑bypass phrasing.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-solving-the-github-traffic-blind-spot-because-14-days-is-not-project-growth-&quot;&gt;4. Solving the GitHub Traffic &quot;Blind Spot&quot; (Because 14 Days Is Not &quot;Project Growth&quot;)&lt;/h3&gt;
&lt;p&gt;I don't know about you, but I got tired of seeing my project's growth through the lens of GitHub’s default 14-day window. GitHub silently wipes daily clone and view metrics after two weeks, leaving open-source maintainers completely blind to long-term adoption trends unless they buy third-party analytics dashboards.&lt;/p&gt;
&lt;p&gt;To solve this, AURA &lt;code&gt;v0.1.0&lt;/code&gt; includes an automated, self-healing snapshot pipeline in &lt;code&gt;.github/workflows/traffic-history.yml&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;how-the-analytics-snapshot-pipeline-works-&quot;&gt;How the Analytics Snapshot Pipeline Works:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Daily Ingestion:&lt;/strong&gt; Executes a daily cron job via GitHub REST API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deduplication:&lt;/strong&gt; Merges metrics into &lt;code&gt;analytics/traffic-history.json&lt;/code&gt; while purging duplicate artifacts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-Healing Merge Logic:&lt;/strong&gt; If concurrent workflow updates cause a direct git push to main to fail, the action politely opens a temporary PR, squash-merges it via actions/github-script, and cleans up after itself:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Auto-merge PR and delete branch
  uses: actions/github-script@v6
  with:
    github-token: ${{ secrets.TRAFFIC_TOKEN }}
    script: |
      const head = `auto/traffic-report-${process.env.GITHUB_RUN_ID}`;
      const { data: prs } = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, head: `${context.repo.owner}:${head}`, state: 'open' });
      if (prs &amp;amp;&amp;amp; prs.length &amp;gt; 0) {
        await github.rest.pulls.merge({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prs[0].number, merge_method: 'squash' });
        await github.rest.git.deleteRef({ owner: context.repo.owner, repo: context.repo.repo, ref: `heads/${head}` });
      }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-community-spotlight-catching-a-subtle-async-race-condition&quot;&gt;5. Community Spotlight: Catching a Subtle Async Race Condition&lt;/h3&gt;
&lt;p&gt;One of the best parts of open-sourcing AURA is having sharp contributors look at the edge cases. Our contributor &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/AmirhosseinAgrest&quot;&gt;Amirhossein Agrest&lt;/a&gt; spotted a subtle async race condition in how case state is updated before disk serialization.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;updateCase()&lt;/code&gt; awaits cross-check evaluation internally. However, in certain batch execution flows, calling scripts invoked it across collections without awaiting the return promise before writing JSON files to disk.&lt;/p&gt;
&lt;p&gt;The result? CLI logs proudly claimed success, while disk artifacts still contained pre-audit state.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// ❌ Potential Race: Fire-and-forget async invocation
for (const entry of entries) updateCase(entry);

// ✅ Fix Pattern: Await all async mutations before serializing to disk
const promises = entries.map(entry =&amp;gt; updateCase(entry));
await Promise.all(promises);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ve logged this issue (shoutout to Amirhossein!) and are pairing it with artificial network-delay adapters in our test suite to guarantee filesystem persistence never outruns in-memory state in the upcoming patch.&lt;/p&gt;
&lt;h3 id=&quot;6-roadmap-what-realistically-comes-next-&quot;&gt;6. Roadmap (What Realistically Comes Next)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;v0.2:&lt;/strong&gt; Programmatic prompt tokenization + TF‑IDF experiments for ranking weak cues (automated test-corpus generation). &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tooling UI:&lt;/strong&gt; Better cross-trigger clustering and a visual rule editor — because manually squinting at hundreds of lines of raw JSON is a fast track to eye bleed, and my laziness is a major driver for automation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML Augmentation:&lt;/strong&gt; Replace some heuristics with small supervised models for cue disambiguation — but only where deterministic rules fall short (no ML for the sake of ML).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;closing-notes&quot;&gt;Closing Notes&lt;/h3&gt;
&lt;p&gt;AURA is not magic, and it doesn't pretend to be. It's a pragmatic stack: deterministic rules, auditable math, and CI that refuses to forget its history.&lt;/p&gt;
&lt;p&gt;If you're looking for a silver bullet, good luck! But if you want a system that is testable, inspectable, and built on sound software engineering principles, welcome aboard.&lt;/p&gt;
&lt;p&gt;⭐ &lt;strong&gt;Check out the repository, inspect the code, and give us a star:&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kate8382/AURA&quot;&gt;kate8382/AURA&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Open an issue, or even better — submit a PR with a failing unit test to help us catch edge cases faster.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28731/aura-v0-deterministic-trigger-extraction-auditable-math-self-healing-analytics-engine</guid>
<pubDate>Sat, 26 Sep 2026 15:51:09 +0000</pubDate>
</item>
<item>
<title>Demystifying the Ghost Failure in Firebase Hosting GitHub Actions</title>
<link>https://coderlegion.com/21748/demystifying-the-ghost-failure-in-firebase-hosting-github-actions</link>
<description>&lt;p&gt;Imagine your pipeline throwing a red error badge, yet your production application reflects fresh updates perfectly. This exact scenario confuses countless developers utilizing the official Firebase Extended GitHub Action. The workflow crashes with a cryptic warning message stating that the supplied version is the current active version, casting doubt on your continuous deployment pipeline reliability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redundant Release Triggers:&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;The pipeline triggers file hashing and bundles content assets accurately, uploading them to Firebase storage infrastructure seamlessly.&lt;br&gt;
Idempotent State Conflicts: When consecutive builds contain zero file modifications, Firebase optimization logic flags the release bundle as structurally identical to the live production build.&lt;br&gt;
Aggressive Exit Code Flags: The deployment utility interprets this identical state as an operational failure, returning a non-zero exit status that forcefully breaks your workflow chain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🛠️ The Modern Implementation Strategy:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Conditional Deployment Guards: Introduce a dynamic file change detection mechanism directly inside your continuous integration step definitions.&lt;br&gt;
Idempotent Workflow Controls: Implement custom workflow scripts leveraging the core Firebase CLI directly instead of using abstract, rigid pre-built actions.&lt;br&gt;
Graceful Exit Optimization: Append standard bash execution overrides to capture identical version notices and rewrite exit statuses cleanly to neutral status levels.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🔗 visit our official site: www.nextbigcreative.com&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21748/demystifying-the-ghost-failure-in-firebase-hosting-github-actions</guid>
<pubDate>Thu, 02 Jul 2026 05:48:04 +0000</pubDate>
</item>
<item>
<title>Lazy SRE's guide to secure systems, part 2: the actions you didn't pin</title>
<link>https://coderlegion.com/19914/lazy-sres-guide-to-secure-systems-part-2-the-actions-you-didnt-pin</link>
<description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://harshit.cloud/blog/lazy-security-part-2-github-actions&quot;&gt;harshit.cloud&lt;/a&gt; on 2026-04-12.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Last March, someone with write access to the &lt;code&gt;trivy-action&lt;/code&gt; repo rewrote 76 of its 77 version tags in place. The tags still resolved to &lt;code&gt;aquasecurity/trivy-action&lt;/code&gt; — they just resolved to different commits than they did the week before. Every pipeline that ran &lt;code&gt;aquasecurity/trivy-action@0.20.0&lt;/code&gt; (and every other tagged version) ran the attacker's commit instead. Secrets exfiltrated. The stolen credentials chained into PyPI and took down LiteLLM. Nobody noticed for hours, because the workflow file diff was still clean.&lt;/p&gt;
&lt;p&gt;This is part 2. &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/lazy-security-part-1-supply-chain&quot;&gt;Part 1&lt;/a&gt; covered npm: the dependencies you didn't read. Part 2 is the same problem one level up: the workflows you didn't pin. Part 3 is the unsexy list — Tailscale, PrivateLink, IAP, the PAT you forgot.&lt;/p&gt;
&lt;p&gt;The thesis from Part 1 stands. The best security work for a small team is the work &lt;em&gt;Future You at 3am&lt;/em&gt; will actually execute. The configuration that makes the wrong thing impossible beats the runbook that only discourages it. With GitHub Actions, &quot;the wrong thing&quot; has gotten very specific over the last twelve months, and the configs to block each variety have gotten correspondingly precise.&lt;/p&gt;
&lt;h3 id=&quot;pinning-is-necessary-but-not-sufficient&quot;&gt;pinning is necessary but not sufficient&lt;/h3&gt;
&lt;p&gt;The first thing the trivy-action incident proves: hash-pinning to &lt;code&gt;@0.20.0&lt;/code&gt; is not pinning. It's a name lookup. The owner of the repo is allowed to rewrite that name. The pin you actually wanted was:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- uses: aquasecurity/trivy-action@9b9a3f5c8a5c7e1b6e4d2f1c9b8a7e6d5c4b3a2f # v0.20.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full forty-character SHA. Immutable. The version comment is so the next reader knows what they're looking at; the SHA is so the workflow runs the code you reviewed.&lt;/p&gt;
&lt;p&gt;Two GitHub features shipped in 2025 that change the math:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SHA pinning enforcement&lt;/strong&gt; (Aug 2025). An org-level policy that &lt;em&gt;fails&lt;/em&gt; workflow runs using unpinned actions, instead of warning about them. Settings → Actions → General → Action pinning. Turn it on. There is no &quot;we'll get to it&quot; version of this toggle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Immutable Releases&lt;/strong&gt; (Oct 2025, GA). Action authors opt in to making release tags non-rewritable after publication. If you publish actions, turn this on for downstream consumers. If you consume actions, prefer ones that have.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lazy stance: enforcement at the org level. The workflow that doesn't have a forty-character SHA fails the run. The PR can't merge. The work of remembering to pin moves from every engineer's head to one setting.&lt;/p&gt;
&lt;p&gt;What this doesn't catch: an attacker who compromises the maintainer account and ships a new tag at a new SHA. The SHA is real. Pinning by SHA doesn't help, because the workflow author &lt;em&gt;will&lt;/em&gt; rev to the new version when they read the maintainer's release notes. Which is the next config.&lt;/p&gt;
&lt;h3 id=&quot;cooldown-is-the-same-trick-that-worked-for-npm&quot;&gt;cooldown is the same trick that worked for npm&lt;/h3&gt;
&lt;p&gt;Part 1's load-bearing config was &lt;code&gt;SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS=48&lt;/code&gt;. The principle: most published malware is detected and pulled within hours. If you can wait, the wait does the work for you.&lt;/p&gt;
&lt;p&gt;The action ecosystem has the same property, with a longer window. &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns&quot;&gt;yossarian's analysis&lt;/a&gt; puts the cooldown that catches most supply-chain attacks at 7-14 days. So:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pinact --min-age 7 .github/workflows/*.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refuses to write a pin younger than seven days. Add to pre-commit, your CI lint, or whatever your dependabot equivalent runs before opening the bump PR.&lt;/p&gt;
&lt;p&gt;For Renovate users, the equivalent lives in the action manager:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;packageRules&quot;: [
    { &quot;matchManagers&quot;: [&quot;github-actions&quot;], &quot;minimumReleaseAge&quot;: &quot;7 days&quot; }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. Same trick, different ecosystem.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://harshit.cloud/images/lazy-security-part-2-github-actions/cooldown-window.gif&quot; alt=&quot;An animated horizontal bar chart in a dark editorial palette showing the share of recent supply-chain action compromises caught by a cooldown of 0, 3, 7, 14, or 21 days. The 0-day bar lands at 3% and the 3-day bar at 38%. The 7-day bar reaches 76% and the 14-day bar reaches 89%, both accented with a brighter cyan and a coral tip. The 21-day bar lands at 94%. A bottom strip notes that the trivy-action force-push was detected at about nine days.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fig. 2 — the wait is doing the work. Seven days closes most of the door; fourteen closes most of the rest.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The empirical question is whether seven days is enough. The trivy-action force-push was detected at about nine — seven would have caught most consumers, not all of them. The cost of fourteen is &quot;your action versions lag upstream by two weeks.&quot; If your action surface is small (most teams are running &lt;code&gt;actions/checkout&lt;/code&gt;, &lt;code&gt;actions/setup-node&lt;/code&gt;, one cloud-login action, maybe a deploy action), set fourteen and forget.&lt;/p&gt;
&lt;h3 id=&quot;pull-request-target-is-the-new-postinstall&quot;&gt;pull_request_target is the new postinstall&lt;/h3&gt;
&lt;p&gt;Part 1 named &lt;code&gt;postinstall&lt;/code&gt; as the single trigger that does the most damage and the single switch (&lt;code&gt;ignore-scripts=true&lt;/code&gt;) that closes the most doors. Actions has the same shape and the same fix.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pull_request_target&lt;/code&gt; runs in the context of the base repository, with access to repository secrets, but is triggered by a PR from a fork. The legitimate use case is small: comment on PRs, label them, run lightweight metadata jobs. The illegitimate use case is enormous: check out the fork's code and execute it. The attack writes itself. Open a fork, modify a script the trusted workflow runs, watch the runner exfiltrate every secret in the env.&lt;/p&gt;
&lt;p&gt;Astral, who maintain &lt;code&gt;uv&lt;/code&gt; and &lt;code&gt;ruff&lt;/code&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://astral.sh/blog/open-source-security-at-astral&quot;&gt;wrote it cleanly&lt;/a&gt;: &quot;these triggers are almost impossible to use securely.&quot; GitHub partially mitigated this in November 2025 by forcing &lt;code&gt;pull_request_target&lt;/code&gt; to always use the default branch's version of the workflow, so an attacker can't push a vulnerable workflow on a feature branch and trigger it. But the foot-cannon still ships loaded if your default-branch workflow checks out PR-head code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://harshit.cloud/images/lazy-security-part-2-github-actions/pull-request-target-contrast.png&quot; alt=&quot;A hand-drawn two-panel napkin. Left panel labeled 'pull_request_target' shows a fork PR boundary as a dashed line, a modified script.sh inside the fork, and a runner on the base side reaching across the boundary while holding a red keyring labeled NPM_TOKEN, AWS_KEY, GH_PAT. Right panel labeled 'pull_request' shows the same setup, but the keyring is replaced by a greyed-out 'secrets.* not in scope' bag. The two panels are structurally identical except for the presence or absence of secrets in the runner.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fig. 3 — same workflow, different trigger, opposite blast radius.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The lazy stance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don't use &lt;code&gt;pull_request_target&lt;/code&gt; unless you've named the specific reason and one other person has signed off.&lt;/li&gt;
&lt;li&gt;If you do, never &lt;code&gt;actions/checkout&lt;/code&gt; the PR head from inside it. Check out the base SHA, do the metadata thing, exit.&lt;/li&gt;
&lt;li&gt;For everything else, use &lt;code&gt;pull_request&lt;/code&gt;. It runs without secrets. Attacker-controlled code stays attacker-jailed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same shape as &lt;code&gt;ignore-scripts=true&lt;/code&gt;. The setting that closes the class.&lt;/p&gt;
&lt;h3 id=&quot;the-safe-defaults-that-go-in-every-workflow&quot;&gt;the safe defaults that go in every workflow&lt;/h3&gt;
&lt;p&gt;The four-line workflow header that does the most work per character:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;permissions:
  contents: read

defaults:
  run:
    shell: bash -euo pipefail {0}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;contents: read&lt;/code&gt; overrides the org-level default. If a step needs to push a tag or open a PR, that job opts back up to &lt;code&gt;contents: write&lt;/code&gt; explicitly. The default is the safe one.&lt;/p&gt;
&lt;p&gt;At the checkout step:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- uses: actions/checkout@&amp;lt;sha&amp;gt; # v4.2.0
  with:
    persist-credentials: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default behavior of &lt;code&gt;actions/checkout&lt;/code&gt; is to leave a credential sitting in &lt;code&gt;.git/config&lt;/code&gt; for the rest of the workflow. Later steps have shipped this credential into uploaded artifacts more than once. Opt out unless a later step in the same job needs to push.&lt;/p&gt;
&lt;p&gt;Three secret-access rules with the same flavor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step-scoped &lt;code&gt;env:&lt;/code&gt;, never workflow-scoped, for any secret.&lt;/li&gt;
&lt;li&gt;Never &lt;code&gt;${{ toJson(secrets) }}&lt;/code&gt;. Exposes every secret in the project to the runner. There is no use case.&lt;/li&gt;
&lt;li&gt;Never &lt;code&gt;secrets: inherit&lt;/code&gt; on reusable workflows. Pass each secret by name. The reusable workflow gets exactly what it asked for.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The trivy-action exfiltration worked partly because secrets were workflow-scoped. The malicious step inherited every credential in the env, not just the one the legitimate scan needed. Step-scoping wouldn't have prevented the credential theft — but it would have bounded the blast radius to one secret instead of all of them.&lt;/p&gt;
&lt;h3 id=&quot;oidc-the-promise-from-part-1&quot;&gt;OIDC, the promise from part 1&lt;/h3&gt;
&lt;p&gt;Part 1 ended on &quot;the next-tier defenses are real, Part 3 names them.&quot; OIDC is the part of that conversation that lives here.&lt;/p&gt;
&lt;p&gt;The trade: instead of storing an &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; in repo secrets and praying nobody exfiltrates it, you configure AWS to trust GitHub's OIDC issuer for a specific repo, branch, and workflow. GitHub mints a short-lived (five-minute) OIDC identity token for the workflow run. The workflow trades that for STS credentials whose lifetime you set (default one hour). Nothing long-lived ever sits in the env.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@&amp;lt;sha&amp;gt; # v4.0.2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://my-bucket
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The role's trust policy restricts the OIDC subject to your exact repo and (ideally) branch. An attacker who compromises a fork PR can't assume the role, because they don't match the trust condition. The OIDC JWT itself lasts five minutes and the STS credential is scoped to whatever you configure (default one hour). Even an exfiltrated credential gets the attacker a bounded window of scoped access, not a permanent IAM user.&lt;/p&gt;
&lt;p&gt;For Google Cloud, the equivalent is Workload Identity Federation. For HashiCorp Vault, the JWT auth backend. Same shape across providers.&lt;/p&gt;
&lt;p&gt;The labor here is genuinely one-time. Configure the trust relationship once per repo, delete the long-lived key, forget about rotation forever. The rotation runbook you're not maintaining is one of the better quiet wins in this post.&lt;/p&gt;
&lt;h3 id=&quot;zizmor-is-the-local-proxy-for-workflows&quot;&gt;zizmor is the local proxy for workflows&lt;/h3&gt;
&lt;p&gt;Part 1's &lt;code&gt;safe-chain&lt;/code&gt; sat in front of every package install and refused malware before bytes hit disk. The action ecosystem's equivalent is &lt;code&gt;zizmor&lt;/code&gt; — a workflow linter that reads your YAML and catches the patterns this post is about, before they merge.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install zizmor
zizmor .github/workflows/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It catches unpinned actions, &lt;code&gt;pull_request_target&lt;/code&gt; with PR-head checkouts, template-injection patterns where attacker-controlled input lands in a &lt;code&gt;run:&lt;/code&gt; string, jobs with excessive permissions. Add it to pre-commit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .pre-commit-config.yaml
- repo: https://github.com/woodruffw/zizmor-pre-commit
  rev: v1.x  # pin the rev, obviously
  hooks:
    - id: zizmor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The principle is identical to safe-chain. Move the security check from &quot;after the incident, in the postmortem&quot; to &quot;before the PR can merge, on the dev machine.&quot; The CI run is the second line of defense. The pre-commit is the first.&lt;/p&gt;
&lt;h3 id=&quot;the-receipts&quot;&gt;the receipts&lt;/h3&gt;
&lt;p&gt;The above stack is approximately one afternoon: org-level SHA pinning enforcement, &lt;code&gt;pinact --min-age 7&lt;/code&gt; or Renovate &lt;code&gt;minimumReleaseAge: 7 days&lt;/code&gt;, the four-line workflow header, &lt;code&gt;persist-credentials: false&lt;/code&gt;, no &lt;code&gt;pull_request_target&lt;/code&gt; with PR-head checkouts, OIDC for every cloud credential, &lt;code&gt;zizmor&lt;/code&gt; in pre-commit.&lt;/p&gt;
&lt;p&gt;It will not catch a maintainer-account compromise that ships clean-looking code which activates weeks later. It will not catch a determined attacker who studies your build and writes a payload that survives every linter and looks innocent at PR review. Nothing in this post will. Part 3 will name the controls that buy partial mitigation against that class: sigstore, npm provenance, reproducible builds, attested deployments. And the ones that exist to make the postmortem readable, not to prevent the incident.&lt;/p&gt;
&lt;p&gt;For a small team, the delta from this post is moving from &quot;we're one tag-rewrite away from a credential theft cascade&quot; to &quot;an attacker would need a credentialed insider, or a fifteen-minute window of luck against a scoped IAM role.&quot; That's the only delta that matters at this scale.&lt;/p&gt;
&lt;p&gt;If you do one thing this week, turn on SHA pinning enforcement at the org level. Everything else gates off that.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19914/lazy-sres-guide-to-secure-systems-part-2-the-actions-you-didnt-pin</guid>
<pubDate>Sun, 07 Jun 2026 21:55:44 +0000</pubDate>
</item>
<item>
<title>The Comforting Lie Of SHA Pinning</title>
<link>https://coderlegion.com/17695/the-comforting-lie-of-sha-pinning</link>
<description>&lt;blockquote&gt;&lt;p&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/&quot;&gt;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In March 2026, Trivy became the latest reminder that software supply chains are, at best, loosely held together with convention and trust.&lt;/p&gt;
&lt;p&gt;A typosquatting attack slipped malicious code into what looked like a legitimate dependency path. The post-mortems are worth reading, and they all converge on a single recommendation: &lt;em&gt;pin your dependencies&lt;/em&gt;. In the GitHub Actions world, that usually translates to &lt;em&gt;use commit SHAs, not tags&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There’s a widely held belief that pinning a GitHub Action to a commit SHA gives you immutability, its what Microsoft/GitHub are recommending, and its what Aqua are recommending. After all, a SHA is content-addressed. It cannot be moved. It cannot be re-tagged. It is, in theory, the most stable reference you can use. The problem with that line of thinking is that the &lt;em&gt;resolution of that SHA is not scoped the way most people assume&lt;/em&gt;. Specifically, GitHub Actions &lt;strong&gt;does not&lt;/strong&gt; meaningfully validate that the commit SHA you reference belongs to the repository you think it does.&lt;/p&gt;
&lt;h3 id=&quot;wait-what-no-thats-not-right-&quot;&gt;Wait, what? No, thats not right...&lt;/h3&gt;
&lt;p&gt;I set up a deliberately small example to test this behaviour.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A “legitimate” action: &lt;code&gt;avaines/blog_gh_sha_pinning_action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A consuming application: &lt;code&gt;avaines/blog_gh_sha_pinning_app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The application references the action in the usual way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;uses: avaines/blog_gh_sha_pinning_action@&amp;lt;some-sha&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/gha-working.png&quot; alt=&quot;Working GitHub Action showing 'Hello World'&quot;&gt;&lt;/p&gt;
&lt;p&gt;So far, so normal.&lt;/p&gt;
&lt;p&gt;Now introduce an attacker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fork the action repository to &lt;code&gt;aidenvaines-cgi/blog_gh_sha_pinning_action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add a malicious step (&lt;em&gt;in my case, just printing output, but in reality this is where you exfiltrate all the fun stuff like secrets and personal data&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/gha-attackers-change.png&quot; alt=&quot;Attacker changes to the GitHub Action in their fork adding malicious content&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next, create a pull request to the consuming application that &lt;em&gt;appears&lt;/em&gt; to simply bump the pinned SHA:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/gha-pr.png&quot; alt=&quot;Attacker raises a PR&quot;&gt;&lt;/p&gt;
&lt;p&gt;The SHA used in the PR comes from the attacker-controlled fork of the action, despite it still being referenced as &lt;code&gt;avaines/blog_gh_sha_pinning_action&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You might reasonably assume one of the following safeguards exists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub validates that the SHA belongs to &lt;code&gt;avaines/blog_gh_sha_pinning_action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Or the workflow fails because the commit cannot be found in the specified repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Neither is true, and that is madness&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The workflow executes successfully!!!!!!!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/gha-exploited.png&quot; alt=&quot;GitHub Actions running the malicious workflow&quot;&gt;&lt;/p&gt;
&lt;p&gt;GitHub resolves the commit SHA, finds a matching object, and executes it, regardless of which fork it originated from.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;wtf.gif&quot; alt=&quot;wft meme&quot; width=&quot;250&quot;&gt;&lt;/p&gt;
&lt;p&gt;From the platform’s perspective, a fork is a separate repository with a shared object graph/history. When the runner resolves the reference, it ultimately looks up the commit in the Git object database; if that object exists and is reachable, it can be used regardless of which fork introduced it. A commit object is globally identifiable. If the SHA exists anywhere reachable, that is apparently sufficient.&lt;/p&gt;
&lt;p&gt;The result is that a pull request can replace a pinned, trusted action with attacker-controlled code without changing the apparent repository reference.&lt;/p&gt;
&lt;p&gt;If the reviewer is scanning for obvious changes like owner, repo name, or tag they will see none.&lt;/p&gt;
&lt;p&gt;Only the SHA changes and with that comes a huge amount of assumed trust. I know that owner, and I know that repository, its just a version bump, and a minor one at that, with that comment next to the tag doing a lot of heavy lifting. We’ve just spent the last few years training people to treat that as &lt;em&gt;best practice&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/gha-pr-delta.png&quot; alt=&quot;GitHub PR Delta&quot;&gt;&lt;/p&gt;
&lt;p&gt;A lot of the current guidance focuses on avoiding tags because they are mutable, which is true: tags can be moved, and relying on them introduces an entirely different risk. Github already has a 'Make tags immutable' feature, but it's optional, therefore, neither used nor can it be trusted as the owner (or attacker) could just disable it.&lt;/p&gt;
&lt;p&gt;Simply switching to SHA pinning does not eliminate the problem, in some respects it makes it worse. Tags &lt;strong&gt;are&lt;/strong&gt; scoped to  &lt;code&gt;owner/repository&lt;/code&gt; because thats how they work. You could argue its harder to compromise that repository rather than hijack it through a forked repository and then writing actual changes to the repository. Whereas a commit object is content-addressed and can be reachable from multiple repositories that share history (e.g. forks)&lt;/p&gt;
&lt;p&gt;I believe the industry advice is a bit of an overcorrection, and we’ve replaced one weak guarantee (mutable tags but scoped to repo) with another vastly worse idea in unscoped SHAs. Yes you should check, yes you should validate it, but tags are human readable, SHAs are not and if you ask yourself &quot;Do I always properly check?&quot; do you? because I can't say I do enough validation 100% of the time.&lt;/p&gt;
&lt;h3 id=&quot;supply-chain-woes&quot;&gt;Supply Chain Woes&lt;/h3&gt;
&lt;p&gt;The Trivy incident is not interesting because of the tool. Though it is the thing that's caused me a lot of bother over the last month, and its symptomatic of the constant supply chain threats we're seeing everywhere. Late last year NPM was basically a skip fire (&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk&quot;&gt;https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack&quot;&gt;https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack&lt;/a&gt;, etc). We've delegated so much behaviour to 3rd parties we can't, and shouldn't implicitly trust. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/is-odd&quot; title=&quot;https://www.npmjs.com/package/is-odd&quot;&gt;https://www.npmjs.com/package/is-odd&lt;/a&gt; package became a bit of a meme for this exact problem, where tool chains like NPM and GitHub Actions place re-usable custom modules/actions/libraries as an attractive off the shelf solution to solve common problems so you don't have to.&lt;/p&gt;
&lt;p&gt;Ironically with the rise of AI, it's now easier to just vibe code the same functionality yourself that use one of these off the shelf resources, and you can still know exactly as much about how security, your data, and to some extent the functionality works with strangely more ownership and a smaller attack service.&lt;/p&gt;
&lt;p&gt;GitHub Actions, in particular exacerbates this somewhat as workflows routinely execute third-party code, the secrets are implicitly available to those workflows and there are multiple ways to extract those at runtime with little audit or oversight. Then to top it all off SHAs are not human friendly and tags are not immutable so review processes tend to focus on &lt;em&gt;what changed&lt;/em&gt;, not *where it came from. Which is all a fragile house of cards, sometimes I miss Jenkins.&lt;/p&gt;
&lt;h3 id=&quot;if-use-shas-is-not-sufficient-what-is-&quot;&gt;If “use SHAs” is not sufficient, what is?&lt;/h3&gt;
&lt;p&gt;At a minimum, we need to introduce &lt;em&gt;provenance checks&lt;/em&gt;. I've said above that SHA's are not human friendly like tags so theres a couple of things we can, and probably should be doing to validate. Obviously that the SHA or tag exists in the right repository and GitHub should enforce tag mutability in my opinion:&lt;/p&gt;
&lt;p&gt;We tend to describe these incidents as “supply chain attacks”, which is accurate but slightly misleading. It implies a complex and sophisticated multi-stage compromise by 1337 h4x0rz. In reality, the weakest link is often much simpler because humans are a bit shit sometimes. SHA pinning being touted as the solution is just security-theater rather than due diligence in an ecosystem that is not helping either.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17695/the-comforting-lie-of-sha-pinning</guid>
<pubDate>Wed, 13 May 2026 09:19:41 +0000</pubDate>
</item>
<item>
<title>Minha Experiência Estruturando uma Pipeline Mobile CI/CD Flutter</title>
<link>https://coderlegion.com/17058/minha-experiencia-estruturando-uma-pipeline-mobile-ci-cd-flutter</link>
<description>&lt;p&gt;Recentemente, enfrentei o desafio de estruturar uma pipeline de CI/CD completa para um aplicativo corporativo desenvolvido em Flutter. O objetivo era claro: automatizar, proteger e agilizar o deploy para Android e iOS. Neste artigo, compartilho os aprendizados, as soluções para os obstáculos encontrados e os exemplos práticos que formaram a espinha dorsal dessa implementação.&lt;/p&gt;
&lt;h2 id=&quot;por-que-investir-em-ci-cd-mobile-&quot;&gt;Por que investir em CI/CD mobile?&lt;/h2&gt;
&lt;p&gt;Em um cenário de desenvolvimento ágil, automatizar os processos de build, teste e deploy não é um luxo, mas uma necessidade. Para aplicações mobile, essa urgência é amplificada: os builds são demorados e consomem recursos, as dependências evoluem rapidamente e cada loja de aplicativos (Google Play e App Store) impõe seu próprio conjunto de regras e exigências. Uma pipeline bem arquitetada garante entregas rápidas, seguras e com a consistência que um ambiente corporativo exige.&lt;/p&gt;
&lt;h2 id=&quot;estrutura-dos-workflows&quot;&gt;Estrutura dos Workflows&lt;/h2&gt;
&lt;p&gt;Para organizar o processo, a pipeline foi modularizada em workflows distintos no GitHub Actions. Cada workflow é uma sequência de jobs que rodam em ambientes específicos, conhecidos como runners.&lt;/p&gt;
&lt;p&gt;Runners: São as máquinas virtuais que executam seus jobs. Para builds Android, utilizamos os runners ubuntu-latest fornecidos pelo GitHub, que são eficientes e econômicos. Para iOS, a compilação exige o macOS. Como os runners macOS do GitHub têm um custo associado, optamos por configurar um runner self-hosted em uma máquina Mac local, garantindo controle total sobre o ambiente e otimização de custos.&lt;/p&gt;
&lt;p&gt;Dividimos nossa esteira em workflows específicos, cada um com sua responsabilidade:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feature-workflow.yml&lt;/code&gt;: Valida código novo, executa lint, testes unitários e análise SAST (Static Application Security Testing), garantindo qualidade e segurança desde o início.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging-workflow.yml&lt;/code&gt;: Prepara builds de homologação, rodando validações e testes automatizados.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mobile-workflow.yml&lt;/code&gt;: Geram builds de produção e fazem o deploy para Google Play e App Store.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;controle-dos-fluxos&quot;&gt;Controle dos Fluxos&lt;/h3&gt;
&lt;p&gt;Para garantir que cada etapa só rode no momento certo e que a pipeline de produção só seja executada após a validação em staging, utilizei gatilhos condicionais. Isso orquestra a transição entre ambientes de forma segura.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  workflow_dispatch: # Permite disparo manual
  workflow_run:
    workflows: [&quot;staging-workflow&quot;] # Nome do workflow que deve terminar
    types:
      - completed # Apenas quando ele for concluído

jobs:
  build:
    # A condição abaixo garante que o job só rode se o disparo for manual
    # ou se o workflow de staging anterior tiver sido concluído com sucesso.
    if: &amp;gt;
      github.event_name == 'workflow_dispatch' ||
      (github.event.workflow_run.conclusion == 'success' &amp;amp;&amp;amp; github.event.workflow_run.head_branch == 'staging')
    runs-on: ubuntu-latest
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso permite que o workflow seja disparado manualmente ou automaticamente após a conclusão bem-sucedida do workflow de staging. O uso do &lt;code&gt;workflow_dispatch&lt;/code&gt; também foi essencial para execuções manuais e testes pontuais.&lt;/p&gt;
&lt;h3 id=&quot;configura-o-do-ambiente&quot;&gt;Configuração do Ambiente&lt;/h3&gt;
&lt;p&gt;Antes de qualquer build, preparei o ambiente instalando Flutter, Java e aplicando cache para dependências, acelerando o processo e evitando downloads desnecessários:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Setup Flutter
  uses: subosito/flutter-action@v2
  with:
    flutter-version: ${{ env.FLUTTER_VERSION }}
    channel: stable
    cache: true

- name: Cache Flutter pub dependencies
  uses: actions/cache@v4
  with:
    path: ~/.pub-cache
    key: ${{ runner.os }}-pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('**/pubspec.lock') }}

- name: Setup Java
  uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No iOS, também utilizei cache para CocoaPods e limpei o ambiente antes de cada build:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Cache CocoaPods
  uses: actions/cache@v4
  with:
    path: ios/Pods
    key: ${{ runner.os }}-pods-${{ hashFiles('**/pubspec.lock') }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;assinatura-dos-apps&quot;&gt;Assinatura dos Apps&lt;/h3&gt;
&lt;p&gt;A assinatura digital é obrigatória para publicação nas lojas. No Android, trabalhei com keystore e no iOS, com certificados e provisioning profiles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Setup iOS certificates
  uses: apple-actions/import-codesign-certs@v2
  with:
    p12-file-base64: ${{ secrets.CERTIFICATE_BASE64 }}
    p12-password: ${{ secrets.CERTIFICATE_PASSWORD }}

- name: Download Provisioning Profiles
  uses: apple-actions/download-provisioning-profiles@v2
  with:
    bundle-id: com.seuapp.id
    profile-type: IOS_APP_STORE
    issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
    api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
    api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O GitHub trabalha de uma forma bem peculiar com certificados, exigindo que o sejam salvos como secret em base64 e decodificado em tempo de execução no runner.&lt;/p&gt;
&lt;p&gt;No Android, o keystore foi salvo via secret em base64 e decodificado no runner:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Setup Android signing
  run: |
    echo &quot;$KEYSTORE_BASE64&quot; | base64 -d &amp;gt; android/app/keystore.jks
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;jobs-de-qualidade&quot;&gt;Jobs de Qualidade&lt;/h3&gt;
&lt;p&gt;Implementei jobs de lint, testes unitários e análise estática de segurança (SAST) para garantir qualidade e segurança:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Lint Flutter
  run: flutter analyze

- name: Testes Unitários
  run: flutter test --coverage

- name: SAST com MobSF
  uses: MobSF/mobsfscan@main
  with:
    args: .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O SAST foi crucial para identificar vulnerabilidades cedo, especialmente em apps que lidam com dados sensíveis. Cada job gera relatórios que ajudam a manter a qualidade do código alta.&lt;/p&gt;
&lt;h3 id=&quot;deploy-automatizado&quot;&gt;Deploy Automatizado&lt;/h3&gt;
&lt;p&gt;Com os builds assinados, o último passo é o deploy, utilizei actions específicas para upload dos builds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Android:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;r0adkll/upload-google-play@v1&lt;/code&gt; para enviar o bundle diretamente à Google Play.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iOS:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;apple-actions/upload-testflight-build@v3&lt;/code&gt; para subir o IPA ao TestFlight.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exemplo de upload para TestFlight:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Upload app to TestFlight
  uses: apple-actions/upload-testflight-build@v3
  with: 
    app-path: ${{ steps.find-ipa.outputs.ipa-path }}
    issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
    api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
    api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;dificuldades-e-aprendizados&quot;&gt;Dificuldades e Aprendizados&lt;/h3&gt;
&lt;p&gt;Um dos maiores desafios foi adaptar o pipeline iOS para rodar em runner Mac self-hosted, já que o GitHub Actions não oferece MacOS gratuito. Também precisei ajustar versões de Java e Gradle para evitar conflitos e falhas silenciosas nos builds Android.&lt;/p&gt;
&lt;p&gt;No fim, consegui uma esteira automatizada, segura e eficiente, que valida, testa, assina e publica os apps nas lojas com mínimo esforço manual. Se você está montando sua pipeline mobile, recomendo investir tempo nos jobs de qualidade, na configuração de secrets e no controle dos fluxos. O resultado compensa!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17058/minha-experiencia-estruturando-uma-pipeline-mobile-ci-cd-flutter</guid>
<pubDate>Fri, 08 May 2026 12:19:10 +0000</pubDate>
</item>
<item>
<title>Deploying a Simple App on K3S in AWS EC2 with GitHub Actions &amp; ECR</title>
<link>https://coderlegion.com/13420/deploying-a-simple-app-on-k3s-in-aws-ec2-with-github-actions-ecr</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Deploying a Simple App on K3S in AWS EC2 with GitHub Actions &amp;amp; ECR&lt;br&gt;
published: true&lt;br&gt;
date: 2025-09-07 12:15:31 UTC&lt;br&gt;
tags: github,kubernetes,kubernetescluster,githubactions&lt;/p&gt;
&lt;h3 id=&quot;canonical-url-https-medium-com-mahinshanazeer-deploying-a-simple-app-on-k3s-in-aws-ec2-with-github-actions-ecr-58e0ea29eb2a&quot;&gt;canonical_url: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@mahinshanazeer/deploying-a-simple-app-on-k3s-in-aws-ec2-with-github-actions-ecr-58e0ea29eb2a&quot;&gt;https://medium.com/@mahinshanazeer/deploying-a-simple-app-on-k3s-in-aws-ec2-with-github-actions-ecr-58e0ea29eb2a&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*_sZvnHHzeRX_BM1ww8GK-A.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this session, we’ll walk through the configuration of K3S on an EC2 instance and deploy a multi-container application with a frontend, backend, and database. The application will run inside a Kubernetes cluster using Deployments and StatefulSets in headless mode. For the setup, we’ll use EC2 to host the cluster, GitHub as our code repository, and GitHub Actions to implement CI/CD.&lt;/p&gt;
&lt;p&gt;If you’re an absolute beginner and not familiar with configuring EC2, I recommend checking out my blog here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/mahinshanazeer/step-by-step-guide-to-launching-an-ec2-instance-on-aws-for-beginners-1ak8&quot;&gt;Step-by-Step Guide to Launching an EC2 Instance on AWS : For Beginners&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This will be an end-to-end project deployment designed for those learning K3S, CI/CD, and Docker. You’ll gain hands-on experience in setting up CI/CD pipelines, writing Dockerfiles, and using Docker Compose. We’ll then move on to deploying the application in K3S, working with Kubernetes manifests, and exploring key components such as Deployments, Services (NodePort and ClusterIP), ConfigMaps, Persistent Volumes (PV), Persistent Volume Claims (PVC), and StatefulSets.&lt;/p&gt;
&lt;p&gt;K3S is a lightweight Kubernetes distribution developed by Rancher (now SUSE). It’s designed to be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Lightweight — small binary, minimal dependencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Easy to install — single command installation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Optimized for edge, IoT, and small clusters — runs well on low-resource machines like Raspberry Pi or small EC2 instances.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Fully compliant — supports all standard Kubernetes APIs and workloads.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In short, K3S simplifies Kubernetes and makes it resource-efficient, making it ideal for single-node clusters, test environments, and learning purposes.&lt;/p&gt;
&lt;p&gt;Log login to the EC2 machine and install k3s first:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://k3s.io/&quot;&gt;K3s&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can install K3S on your machine using the following single command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update -y &amp;amp;&amp;amp; sudo apt upgrade -y
curl -sfL https://get.k3s.io | sh - 
# Check for Ready node, takes ~30 seconds 
sudo k3s kubectl get node 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*WthGdHM5my4mFViKNuItYg.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Installation of k3s&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the installation is completed, the output should be similar to this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*u_KORCKe0I_kU2uL1SSUJw.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Kubectl node status&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the cluster is up and running, we can move on to the application. You can refer to the following repository for the demo To-Do List app. Before cloning the repository, make sure Docker is installed on the machine to build and test the application. For installing Docker, refer to the following URL:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;Ubuntu&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#run the following command first to remove conficting packages

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release &amp;amp;&amp;amp; echo &quot;${UBUNTU_CODENAME:-$VERSION_CODENAME}&quot;) stable&quot; | \
  sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null

sudo apt-get update

#Installing Docker
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

#Now verify the installtion.
sudo docker run hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s dive into the demo application. The Application Stack:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Frontend: React.js  &lt;/li&gt;
&lt;li&gt;Backend API: Node.js + Express  &lt;/li&gt;
&lt;li&gt;Database: MongoDB  &lt;/li&gt;
&lt;li&gt;Containerization &amp;amp; Registry: Docker + AWS ECR  &lt;/li&gt;
&lt;li&gt;Orchestration &amp;amp; Service Management: Kubernetes (K3s)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, let’s clone the application repository to your local machine.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mahinshanazeer/docker-frontend-backend-db-to_do_app.git&quot;&gt;GitHub - mahinshanazeer/docker-frontend-backend-db-to_do_app: Simple Application with Frontend + Backened + DB&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/mahinshanazeer/docker-frontend-backend-db-to_do_app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*sMBBrqHHy6RH0rEo4CxHAg.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Clone the github application&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the repository is cloned, switch to the application directory and check for the Docker Compose file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*3SK0wgqcLyIlq2EApUyKpA.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Directory structure&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3.8&quot;
services:
  web:
    build:
      context: ./frontend
      args:
        REACT_APP_API_URL: ${REACT_APP_API_URL}
    depends_on:
      - api
    ports:
      - &quot;3000:80&quot;
    networks:
      - network-backend
    env_file:
      - ./frontend/.env

  api:
    build: ./backend
    depends_on:
      - mongo
    ports:
      - &quot;3001:3001&quot;
    networks: 
      - network-backend
  
  mongo:
    build: ./backend-mongo  
    image: docker-frontend-backend-db-mongo
    restart: always
    volumes: 
      - ./backend-mongo/data:/data/db
    environment: 
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: adminhackp2025
    networks: 
      - network-backend

networks:
  network-backend:

volumes: 
  mongodb_data:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the Docker Compose file, you’ll see sections for web, api, and mongo. Let’s dive into each directory and review the Dockerfiles. The Docker Compose file builds the Docker images using the Dockerfiles located in their respective directories.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
root@ip-172-31-22-24:/home/ubuntu/docker-frontend-backend-db-to_do_app/frontend# cd /home/ubuntu/docker-frontend-backend-db-to_do_app/frontend
root@ip-172-31-22-24:/home/ubuntu/docker-frontend-backend-db-to_do_app/frontend# cat Dockerfile 
# ---------- Build Stage ----------
FROM node:16-alpine AS build

WORKDIR /app

# Copy dependency files first
COPY package*.json ./

# Install dependencies
RUN npm install --legacy-peer-deps

# Copy rest of the app
COPY . .

# Build the React app
RUN npm run build

# ---------- Production Stage ----------
FROM nginx:alpine

# Copy custom nginx config if you have one
# COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copy build output from build stage
COPY --from=build /app/build /usr/share/nginx/html

# Expose port 80
EXPOSE 80

# Start nginx
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]

***

root@ip-172-31-22-24:/home/ubuntu/docker-frontend-backend-db-to_do_app/backend# cd /home/ubuntu/docker-frontend-backend-db-to_do_app/backend
root@ip-172-31-22-24:/home/ubuntu/docker-frontend-backend-db-to_do_app/backend# cat Dockerfile 
FROM node:10-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3001

***

root@ip-172-31-22-24:/home/ubuntu/docker-frontend-backend-db-to_do_app/backend# cd /home/ubuntu/docker-frontend-backend-db-to_do_app/backend-mongo/
root@ip-172-31-22-24:/home/ubuntu/docker-frontend-backend-db-to_do_app/backend-mongo# cat Dockerfile 
FROM mongo:6.0
EXPOSE 27017
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open the .env file in the frontend directory and update the IP address to your EC2 public IP. This environment variable is used by the frontend to connect to the backend, which runs on port 3001.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vi /home/ubuntu/docker-frontend-backend-db-to_do_app/frontend/.env

#edit the IP address, I have updated my EC2 public IP
~~~
REACT_APP_API_URL=http://54.90.185.176:3001/
~~~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also cross-check the total number of APIs using the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep -R &quot;router.&quot; backend/ | grep &quot;(&quot;
grep -R &quot;app.&quot; backend/ | grep &quot;(&quot;
grep -R &quot;app.&quot; backend/ | grep &quot;(&quot; | wc -l
grep -R &quot;router.&quot; backend/ | wc -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s test the application by spinning up the containers. Navigate back to the project’s root directory and run the Docker Compose command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /home/ubuntu/docker-frontend-backend-db-to_do_app
docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you run the command, Docker will start building the images and spin up the containers as soon as the images are ready&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*aADXzg2UJlfmx_iao7tazg.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;building docker containers&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Wait until you see the ‘built’ and ‘created’ messages. Once the containers are up and running, use docker ps -a to verify the status.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*l7deKMuT9cRlE99Bncv8Jw.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;build completed and containers started.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker ps -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*_2gFMleEoB5YohK868DJdg.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;docker processes&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the Docker containers are up and running, verify that the application is working as expected.&lt;/p&gt;
&lt;p&gt;Once the Docker containers are up and running, verify that the application is working as expected. Open the server’s IP address on port 3000. You can confirm the mapped ports in the Docker Compose file or by checking the docker ps -a output. Here, port 3000 is for the frontend web app, port 3001 is for the backend, and MongoDB runs internally on port 27017 without public access. In this example, load the website by entering 54.90.185.176:3000 in your browser.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*IBWj0PwOHw16LSe-DSv-iQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Application interface&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you’re using Chrome, right-click anywhere on the page and open Inspect &amp;gt; Network. Then click on Add Todo to verify that the list updates correctly, and the network console shows a 200 status response&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*9RjZ4aleKcaqonPj3T2J9Q.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;checking the network&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/994/1*WKFtfbYRAqQiM7FSZOeDPQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Application testing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Click on the buttons and try to add new file, and verify the status codes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*7-biYdl98qtwip0M7q_7QQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Testing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So far, everything looks good. Now, let’s proceed with the Kubernetes deployment. To configure resources in Kubernetes, we’ll need to create manifest files in YAML format. You can create these files as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /home/ubuntu/manifest
touch api-deployment.yaml api-service.yaml image_tag.txt mongo-secret.yaml mongo-service.yaml mongo-statefulset-pv-pvc.yaml web-deployment.yaml web-env-configmap.yaml web-service.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now edit each file and add the following contents:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;api-deployment.yaml:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Defines how the backend API should run inside the cluster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Creates 2 replicas of the API for reliability.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Uses environment variables from secrets for MongoDB authentication.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Ensures the API pods always restart if they fail.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Provides scalability and fault tolerance for the backend service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rolling Update: Gradually replaces old pods with new ones. Uses fewer resources, minimal downtime if tuned, but users may hit bad pods if the new version is faulty.&lt;/p&gt;
&lt;p&gt; Rolling = efficient and native.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: 495549341534.dkr.ecr.us-east-1.amazonaws.com/hackp2025:api-20250907111542
          ports:
            - containerPort: 3001
          env:
            - name: MONGO_INITDB_ROOT_USERNAME
              valueFrom:
                secretKeyRef:
                  name: mongo-secret
                  key: username
            - name: MONGO_INITDB_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mongo-secret
                  key: password
      restartPolicy: Always
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;api-service.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Exposes the API deployment to the outside world.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Type NodePort makes the service reachable via :31001.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Ensures frontend or external clients can communicate with the backend.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Acts as a bridge between users/frontend and the backend API.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: api
  labels:
    app: api
spec:
  type: NodePort
  selector:
    app: api
  ports:
    - port: 3001 # internal cluster port
      targetPort: 3001 # container port
      nodePort: 31001 # external port on the node
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;mongo-secret.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Stores sensitive information (username &amp;amp; password) in base64-encoded format.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Used by both the API and MongoDB.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Keeps credentials out of plain-text manifests.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Secure way to handle database credentials.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: mongo-secret
type: Opaque
data:
  # Base64 encoded values
  username: YWRtaW4= # &quot;admin&quot;
  password: YWRtaW5oYWNrcDIwMjU= # &quot;adminhackp2025&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;mongo-service.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Defines the MongoDB service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;- ClusterIP: None&lt;/strong&gt; makes it a &lt;strong&gt;headless service&lt;/strong&gt; , required for StatefulSets.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Allows pods to connect to MongoDB by DNS (e.g., mongo-0.mongo).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Provides stable networking for MongoDB StatefulSet pods.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: mongo
  labels:
    app: mongo
spec:
  ports:
    - port: 27017
      targetPort: 27017
  selector:
    app: mongo
  clusterIP: None # headless service for StatefulSet
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;mongo-statefulset-pv-pvc.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Handles the &lt;strong&gt;database persistence and StatefulSet definition&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;- PersistentVolume (PV):&lt;/strong&gt; Reserves storage (5Gi).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;- PersistentVolumeClaim (PVC):&lt;/strong&gt; Ensures pods can claim storage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;- StatefulSet:&lt;/strong&gt; Guarantees stable network identity and persistent storage for MongoDB.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Ensures &lt;strong&gt;MongoDB data is preserved&lt;/strong&gt; even if the pod restarts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blue/Green Deployment: Runs two environments (Blue = live, Green = new). Traffic is switched instantly once Green is ready. Near-zero downtime and easy rollback, but requires double resources and is more complex for stateful apps.&lt;/p&gt;
&lt;p&gt; Blue/Green = safer cutover, higher cost.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# PersistentVolume for Green MongoDB
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongo-green-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /root/hackpproject/data-green # separate path for green
  persistentVolumeReclaimPolicy: Retain
  storageClassName: &quot;&quot; # Must match PVC in StatefulSet

---

# StatefulSet for Green MongoDB
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongo-green
  labels:
    app: mongo
    version: green
spec:
  serviceName: mongo # existing headless service
  replicas: 1
  selector:
    matchLabels:
      app: mongo
      version: green
  template:
    metadata:
      labels:
        app: mongo
        version: green
    spec:
      containers:
        - name: mongo
          image: 495549341534.dkr.ecr.us-east-1.amazonaws.com/hackp2025:db-20250907111542
          ports:
            - containerPort: 27017
          env:
            - name: MONGO_INITDB_ROOT_USERNAME
              valueFrom:
                secretKeyRef:
                  name: mongo-secret
                  key: username
            - name: MONGO_INITDB_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mongo-secret
                  key: password
          volumeMounts:
            - name: mongo-data
              mountPath: /data/db
  volumeClaimTemplates:
    - metadata:
        name: mongo-data
      spec:
        accessModes: [&quot;ReadWriteOnce&quot;]
        resources:
          requests:
            storage: 5Gi
        storageClassName: &quot;&quot; # binds to the pre-created PV
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;web-deployment.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Defines how the &lt;strong&gt;frontend (React.js app)&lt;/strong&gt; should run.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Runs &lt;strong&gt;2 replicas&lt;/strong&gt; for high availability.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Pulls &lt;strong&gt;API endpoint from ConfigMap&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Resource requests/limits ensure fair scheduling.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Deploys the &lt;strong&gt;UI&lt;/strong&gt; and links it to the backend API via config.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rolling Update: Gradually replaces old pods with new ones. Uses fewer resources, minimal downtime if tuned, but users may hit bad pods if the new version is faulty.&lt;/p&gt;
&lt;p&gt; Rolling = efficient and native.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3         
      maxUnavailable: 0   
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: 495549341534.dkr.ecr.us-east-1.amazonaws.com/hackp2025:web-20250907111542
          ports:
            - containerPort: 3000
              protocol: TCP
          env:
            - name: REACT_APP_API_URL
              valueFrom:
                configMapKeyRef:
                  name: web-env
                  key: REACT_APP_API_URL
          resources:
            requests:
              cpu: &quot;200m&quot;
              memory: &quot;1024Mi&quot;
            limits:
              cpu: &quot;2&quot;
              memory: &quot;2Gi&quot;
      restartPolicy: Always
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;web-env-configmap.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Stores non-sensitive environment variables.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Defines the &lt;strong&gt;API endpoint&lt;/strong&gt; for the frontend (REACT_APP_API_URL).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Can be updated easily without rebuilding Docker images.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Provides flexibility to change configuration without redeploying code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: web-env
  labels:
    app: web
data:
  REACT_APP_API_URL: http://98.86.216.31:31001
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;web-service.yaml&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;Exposes the frontend to users.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;- Type NodePort&lt;/strong&gt; makes it available externally at :32000.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;ul&gt;
&lt;li&gt;Maps port 3000 (service) → port 80 (container).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt; Importance: Allows &lt;strong&gt;end-users to access the web app&lt;/strong&gt; from their browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: web
  labels:
    app: web
spec:
  type: NodePort
  selector:
    app: web # Must match Deployment labels
  ports:
    - name: http
      port: 3000 # Service port inside cluster
      targetPort: 80 # Container port
      nodePort: 32000 # External port accessible from outside
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have now moved all the manifest files to /root/hackpproject/manifestfiles.&lt;/p&gt;
&lt;p&gt;Once the manifests are finalised, the next step is to create a repository in ECR to push the build artefact images.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Steps to Create an ECR Repository:&lt;br&gt;
1.Log in to AWS Console → Go to the ECR service.  &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create Repository  &lt;/li&gt;
&lt;li&gt;Click Create repository.  &lt;/li&gt;
&lt;li&gt;Select Private repository.  &lt;/li&gt;
&lt;li&gt;Enter repository names — prodimage. (In this case, we are creating a single repository for all those 3 images)  &lt;/li&gt;
&lt;li&gt;Leave others as default and click Create repository.  &lt;/li&gt;
&lt;li&gt;Authenticate Docker with ECR&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*tMvjSvSO_Unn3MZgrgSnEA.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Step 1: Finding the ECR&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*mqSNSwlf5a8EgXNNPf9SRQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Step 2: Creating Repository&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*mTD3xMeYfWtP12A24O_NLA.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Step 3: onfiguring Repository&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the registry is created, you can proceed with the CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*wpOVbkhkdpbgqnPF080prA.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Reposiroty end point&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s create a GitHub Actions pipeline to deploy the code to the EC2 K3S cluster. The first step is to configure GitHub Actions with access to the repository, ECR, and the EC2 instance via SSH.&lt;/p&gt;
&lt;p&gt;Navigate to the project directory and create a folder named ‘.github’. Inside this folder, create a file named ‘ci-cd.yml’.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir .github
cd .github
touch ci-cd.yml
vi ci-cd.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ci-cd.yml file is the core configuration file for GitHub Actions that defines your CI/CD pipeline. Now use the following script in that ci-cd.yml file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: CI/CD Pipeline

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13420/deploying-a-simple-app-on-k3s-in-aws-ec2-with-github-actions-ecr</guid>
<pubDate>Tue, 24 Mar 2026 10:36:12 +0000</pubDate>
</item>
</channel>
</rss>