<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged devops-infra</title>
<link>https://coderlegion.com/tag/devops-infra</link>
<description>Powered by Question2Answer</description>
<item>
<title>Webernetes: Kubernetes in your browser?</title>
<link>https://coderlegion.com/24792/webernetes-kubernetes-in-your-browser</link>
<description>&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ngrok/webernetes&quot;&gt;Webernetes&lt;/a&gt;&lt;/strong&gt; is an experimental project developed by &lt;strong&gt;ngrok&lt;/strong&gt; that ports a functional subset of Kubernetes directly into the browser, executing completely on the client side without relying on any backend server infrastructure. Built primarily as an interactive simulator, it allows users to boot up isolated virtual clusters and define simulated container images directly in TypeScript / JavaScript. Instead of pulling heavy container binaries or running Docker daemons, Webernetes mimics core control plane mechanics, pod scheduling, and network handling down to HTTP routing, service discovery, and deployment strategies right in the browser runtime.&lt;/p&gt;
&lt;p&gt;Here is a test project that I spun-up for Webernetes:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://ndouglas-edera.github.io/my-webernetes-demo&quot;&gt;https://ndouglas-edera.github.io/my-webernetes-demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When compared to spinning up a real infrastructure cluster on cloud providers or managed platforms, Webernetes eliminates the operational weight of traditional cluster management. A real production or staging Kubernetes deployment requires provisioning control nodes, configuring container runtimes like &lt;code&gt;containerd&lt;/code&gt;, managing complex networking overlays, and continuously maintaining underlying compute instances. Webernetes bypasses this entirely by mocking container lifecycles and TCP/HTTP traffic natively in JavaScript. All of the instructions can be easily scripted in a 100 lines or less of TypeScript through a centralised &lt;code&gt;main.ts&lt;/code&gt; source file:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ndouglas-edera/my-webernetes-demo/edit/main/src/main.ts&quot;&gt;https://github.com/ndouglas-edera/my-webernetes-demo/edit/main/src/main.ts&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This translates to near-instantaneous cluster startup times, with zero setup friction for end users, and a radically reduced maintenance burden for educators, technical writers, or visual platform creators who want to showcase Kubernetes concepts without wrestling with real cloud instances. In my cases, I want to build cluster labs where users can perform insecure behaviours, container escapes, and other Kubernetes security test scenarios, without actually allowing those same users to abuse cloud resources through cryptominers, exfiltrate real credentials, or any other obviously undesirable outcomes.&lt;/p&gt;
&lt;p&gt;From a financial perspective, the cost savings of moving simulation workloads to Webernetes are substantial. Hosting interactive tutorials, live documentation, or architecture demos on traditional cloud infrastructure incurs ongoing costs for control planes, compute nodes, load balancers, and egress bandwidth that accumulate regardless of traffic volume. Webernetes shifts the entire computational load to the visitor’s device, completely cutting out backend compute, hosting, and API costs. Because the browser executes the cluster logic locally, the hosting requirements collapse down to serving static web assets, making long-term interactive Kubernetes content practically free to maintain forever. Essentially, I can recreate a sort of &quot;fake&quot; &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.katacoda.com/&quot;&gt;Katacoda&lt;/a&gt;&lt;/strong&gt; (now deprecated) or &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://instruqt.com/interactive-tours?metadata_cid=1610599&amp;amp;utm_campaign=GA-Brand-interactive-tours&amp;amp;utm_content=Ad_Group&amp;amp;utm_medium=GoogleAds&amp;amp;utm_source=Metadata&amp;amp;utm_term=Landingpage&amp;amp;cid=21486515476&amp;amp;aid=816048007659&amp;amp;keyword=instruqt&amp;amp;matchtype=p&amp;amp;gad_source=1&amp;amp;gad_campaignid=21486515476&amp;amp;gbraid=0AAAAACc8aALV31zTeJa2pNyo-M04bJgpm&amp;amp;gclid=Cj0KCQjw4orUBhCjARIsAIbF3qxXr9etErIGzMMi1M2kjFlQB82wy5ZFeGQ2YgZ6eu6wjiMqzp0csSAaAo0bEALw_wcB&quot;&gt;Instruqt&lt;/a&gt;&lt;/strong&gt; lab that users can mess around with at any time.&lt;/p&gt;
&lt;p&gt;Evaluating scale against a physical homelab setup, such as my existing cluster of Raspberry Pis, reveals a distinct shift in trade-offs between my existing physical resource limits and seemingly limitless browser-bound agility. While a physical Raspberry Pi homelab provides actual ARM nodes, real Linux kernels, and genuine network interfaces capable of running real Docker containers, it demands physical power, cooling, cabling, and ongoing hardware upkeep. Furthermore, a homelab cluster is bounded by fixed hardware capacity and local network constraints, making it tricky to share scale horizontally with thousands of concurrent online users. In contrast, Webernetes scales endlessly across web visitors because each user instantly gets their own isolated, sandboxed cluster powered by their local machine's memory and CPU. Although Webernetes cannot run real un-ported container images, mount true block storage, or handle real raw IP/UDP protocols like physical hardware can, its client-side architecture makes it far superior for scaling interactive learning experiences, rapid prototyping, and reproducible architectural demos to a global audience without ever burning out a single physical node.&lt;/p&gt;
&lt;p&gt;If anything in this project sounds exciting, or if you have questions for me on my usage of this project, feel free to ask in the comment sections. I hope you're as excited about Webernetes as I am.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24792/webernetes-kubernetes-in-your-browser</guid>
<pubDate>Mon, 17 Aug 2026 15:55:35 +0000</pubDate>
</item>
<item>
<title>Dashboard Design for Incident Response</title>
<link>https://coderlegion.com/21769/dashboard-design-for-incident-response</link>
<description>&lt;h3 id=&quot;most-dashboards-answer-one-question-is-everything-okay-&quot;&gt;Most dashboards answer one question: &lt;em&gt;Is everything okay?&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;During an incident, nobody's asking that.&lt;/p&gt;
&lt;p&gt;The real question: &lt;em&gt;What broke, where, and what changed?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most dashboards fail at incidents because they were built for monitoring, not troubleshooting.&lt;/p&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;A typical dashboard shows CPU, memory, disk, network, requests, uptime.&lt;/p&gt;
&lt;p&gt;Useful for routine checks.&lt;/p&gt;
&lt;p&gt;During an outage? Just noise.&lt;/p&gt;
&lt;p&gt;You're not looking for reassurance. You're looking for evidence.&lt;/p&gt;
&lt;h3 id=&quot;two-different-jobs&quot;&gt;Two Different Jobs&lt;/h3&gt;
&lt;p&gt;Most teams put everything on one dashboard. That's a compromise that doesn't work for either job.&lt;/p&gt;
&lt;p&gt;Monitoring dashboard: Is the platform healthy? SLAs being met? Resources used correctly?&lt;/p&gt;
&lt;p&gt;Incident dashboard: What failed? When? What changed? Where do I look next?&lt;/p&gt;
&lt;p&gt;Same tools, different purposes.&lt;/p&gt;
&lt;h3 id=&quot;what-works-during-an-outage&quot;&gt;What Works During an Outage&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Error rate front and center.&lt;/strong&gt; 5XX errors, exceptions, failed transactions. Failures tell the story faster than CPU metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeline on the graph.&lt;/strong&gt; Mark deployments, infrastructure changes, scaling events. Most incidents start right after something changed. Make this visible in one second.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependency health.&lt;/strong&gt; A healthy app talking to a dead database is not healthy. Dependencies often point to root cause faster than app metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Golden signals.&lt;/strong&gt; Latency, traffic, errors, saturation. These beat hundreds of infrastructure metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Logs visible.&lt;/strong&gt; Top exceptions, error spikes, failed endpoints. Reduce tab-switching during incidents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Service map.&lt;/strong&gt; Which services depend on the failing one? Visual dependency maps answer this instantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alert state.&lt;/strong&gt; Which alerts fired? Which started first? First alert usually beats alert #100 for root cause.&lt;/p&gt;
&lt;h3 id=&quot;the-test&quot;&gt;The Test&lt;/h3&gt;
&lt;p&gt;For every panel: How does this help me resolve the incident faster?&lt;/p&gt;
&lt;p&gt;If the answer isn't obvious, remove it.&lt;/p&gt;
&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;
&lt;p&gt;EKS outage. Don't show cluster CPU and memory.&lt;/p&gt;
&lt;p&gt;Show:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Failed requests by service&lt;/li&gt;
&lt;li&gt;Pod restarts&lt;/li&gt;
&lt;li&gt;Readiness failures&lt;/li&gt;
&lt;li&gt;Recent deployments&lt;/li&gt;
&lt;li&gt;HPA scaling events&lt;/li&gt;
&lt;li&gt;Dependency latency&lt;/li&gt;
&lt;li&gt;Top exceptions&lt;/li&gt;
&lt;li&gt;Queue backlogs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One tells you the cluster exists. The other helps you fix it.&lt;/p&gt;
&lt;h3 id=&quot;the-point&quot;&gt;The Point&lt;/h3&gt;
&lt;p&gt;Monitoring dashboards tell you something broke.&lt;/p&gt;
&lt;p&gt;Incident dashboards help you figure out why.&lt;/p&gt;
&lt;p&gt;During an outage, only the second one matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;devops-sre-monitoring-kubernetes-incidentresponse-dashboard&quot;&gt;DevOps #SRE #Monitoring #Kubernetes #IncidentResponse #Dashboard&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21769/dashboard-design-for-incident-response</guid>
<pubDate>Thu, 02 Jul 2026 15:12:56 +0000</pubDate>
</item>
<item>
<title>I released **oteldoctor v0.1.0** — an open-source Go CLI for checking OpenTelemetry Collector config</title>
<link>https://coderlegion.com/18690/released-oteldoctor-v0-an-open-source-go-cli-for-checking-opentelemetry-collector-config</link>
<description>&lt;p&gt;OpenTelemetry Collector configs are easy to grow and hard to reason about.&lt;/p&gt;
&lt;p&gt;A config can be valid and still have production risks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;exporters:
  otlphttp:
    endpoint: http://backend.example.com
    headers:
      X-API-KEY: &quot;hardcoded-secret&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service:
  pipelines:
    traces:
      processors: [batch, memory_limiter]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the kinds of things oteldoctor is designed to catch.&lt;/p&gt;
&lt;h3 id=&quot;what-it-does&quot;&gt;What it does&lt;/h3&gt;
&lt;p&gt;oteldoctor checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;undefined receivers/processors/exporters&lt;/li&gt;
&lt;li&gt;missing &lt;code&gt;memory_limiter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;wrong processor order&lt;/li&gt;
&lt;li&gt;missing retry/queue config&lt;/li&gt;
&lt;li&gt;hardcoded secrets&lt;/li&gt;
&lt;li&gt;plain HTTP exporters&lt;/li&gt;
&lt;li&gt;debug exporters in production&lt;/li&gt;
&lt;li&gt;high-cardinality attributes&lt;/li&gt;
&lt;li&gt;missing sampling/filtering hints&lt;/li&gt;
&lt;li&gt;semantic convention issues&lt;/li&gt;
&lt;li&gt;Kubernetes readiness gaps&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;commands&quot;&gt;Commands&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;oteldoctor analyze ./deploy --profile production
oteldoctor analyze ./deploy --format json
oteldoctor analyze ./deploy --format sarif &amp;gt; oteldoctor.sarif
oteldoctor graph collector.yaml --format mermaid
oteldoctor explain OTEL-SEC-202
oteldoctor fix collector.yaml --dry-run
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;install&quot;&gt;Install&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go install github.com/firfircelik/oteldoctor/cmd/oteldoctor@v0.1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;why-it-exists&quot;&gt;Why it exists&lt;/h3&gt;
&lt;p&gt;This is not a replacement for Collector validation.&lt;/p&gt;
&lt;p&gt;It is more like a production-readiness layer for Collector configs.&lt;/p&gt;
&lt;p&gt;The goal is to catch reliability, security, cost, semantic, and Kubernetes deployment risks earlier — ideally in CI, before the config is deployed.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/firfircelik/oteldoctor&quot;&gt;https://github.com/firfircelik/oteldoctor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you work with OpenTelemetry Collector, I’d really appreciate feedback, issue reports, and real-world config edge cases.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18690/released-oteldoctor-v0-an-open-source-go-cli-for-checking-opentelemetry-collector-config</guid>
<pubDate>Fri, 22 May 2026 14:34:21 +0000</pubDate>
</item>
<item>
<title>Tofu Provider Fabric</title>
<link>https://coderlegion.com/15752/tofu-provider-fabric</link>
<description>&lt;p&gt;OpenTofu modules for simple cross-cloud VM and networking workflows on AWS and OpenStack. Built to keep small infrastructure setups practical, reusable, and easy to follow.&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/15752/tofu-provider-fabric</guid>
<pubDate>Fri, 24 Apr 2026 21:16:58 +0000</pubDate>
</item>
<item>
<title>Building Friday: A Multi-Provider AI Agent That Lives in Your Terminal</title>
<link>https://coderlegion.com/14514/building-friday-a-multi-provider-ai-agent-that-lives-in-your-terminal</link>
<description>&lt;p&gt;Building Friday: A Multi-Provider AI Agent That Lives in Your Terminal&lt;/p&gt;
&lt;p&gt;Every DevOps Engineer has a terminal open. What if that terminal could think?&lt;br&gt;
Friday - an open-source, multi-provider AI agent that runs entirely in your terminal. No browser tabs, no Electron apps, no subscriptions bundling features you don't need. Just a clean, minimal CLI that connects to Gemini, ChatGPT, Claude, GitHub Copilot, or your own local Ollama server - and lets you switch between them with a single command.&lt;br&gt;
In this post, I'll walk through why I built it, the architecture behind it, and how you can install it in one command.&lt;br&gt;
In this post, I'll walk through why I built it, the architecture behind it, and how to install it with a single command.&lt;br&gt;
What Friday Delivers&lt;br&gt;
Friday is a terminal-native AI agent with three core capabilities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Multi-Provider Chat&lt;br&gt;
Connect to any major AI provider through a unified interface:&lt;br&gt;
The Problem&lt;br&gt;
Modern AI workflows are fragmented:&lt;br&gt;
Multiple browser tabs (ChatGPT, Claude, Gemini)&lt;br&gt;
Constant copy-paste between the terminal and AI tools&lt;br&gt;
No direct execution - only suggestions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even simple queries, like checking disk usage, require manual execution after being asked.&lt;br&gt;
The requirement was clear:&lt;br&gt;
 A single interface that supports multiple models and can perform real actions on the machine.&lt;br&gt;
What Friday Delivers&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Unified Multi-Provider Interface&lt;br&gt;
Friday integrates major AI providers into one CLI:&lt;br&gt;
available providersModel availability is dynamically fetched via APIs - ensuring accuracy without manual updates.&lt;/li&gt;
&lt;li&gt;Agentic Capabilities for DevOps&lt;br&gt;
Friday is built for execution, not just conversation.&lt;br&gt;
Core capabilities:&lt;br&gt;
Shell command execution (with confirmation)&lt;br&gt;
File operations (read, write, search)&lt;br&gt;
Web search integration&lt;br&gt;
System diagnostics (CPU, memory, disk)&lt;br&gt;
Python execution&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example:&lt;br&gt;
 Instead of suggesting commands, Friday directly performs tasks like file discovery or diagnostics.&lt;br&gt;
All critical operations require explicit confirmation, ensuring safety.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Voice Interaction&lt;br&gt;
Voice support enables hands-free interaction:&lt;br&gt;
Toggle via /voice&lt;br&gt;
Speak queries with empty input&lt;br&gt;
Configure pitch, speed, and voice profile&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is particularly useful during troubleshooting or multitasking scenarios.&lt;br&gt;
Architecture Overview&lt;br&gt;
Friday follows a modular, provider-agnostic architecture:&lt;br&gt;
Application Layer (CLI + Commands)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Friday Client (Abstraction Layer)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Providers (Gemini, OpenAI, Claude, Copilot, Ollama)&lt;br&gt;
Provider Abstraction&lt;br&gt;
Each provider implements a common interface:&lt;br&gt;
class BaseProvider(ABC):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def initialize(self): ...
def chat(self, message, history, tools): ...
@property
def model(self): ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This enables seamless extensibility with minimal integration effort.&lt;br&gt;
Tool System&lt;br&gt;
Tools are defined once and adapted across providers:&lt;br&gt;
@register_tool&lt;br&gt;
def get_system_info():&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A universal schema ensures compatibility across different APIs.&lt;br&gt;
Dynamic Model Discovery&lt;br&gt;
Friday queries provider APIs at runtime:&lt;br&gt;
Eliminates outdated configurations&lt;br&gt;
Supports local and remote Ollama instances&lt;br&gt;
Ensures real-time model availability&lt;/p&gt;
&lt;p&gt;Command System&lt;br&gt;
A structured command interface simplifies interaction:&lt;br&gt;
/login         Connect provider&lt;br&gt;
/switch        Change provider&lt;br&gt;
/model         Select model&lt;br&gt;
/logout        Remove credentials&lt;br&gt;
/voice         Toggle voice&lt;br&gt;
/voice config  Configure voice&lt;br&gt;
/tools         List tools&lt;br&gt;
/help          Show commands&lt;br&gt;
bye            Exit&lt;br&gt;
Credentials are securely stored with restricted permissions.&lt;br&gt;
User Interface&lt;br&gt;
Built using the Rich library, the UI is clean and minimal:&lt;br&gt;
Color-coded provider panels&lt;br&gt;
Markdown rendering with syntax highlighting&lt;br&gt;
Interactive menus&lt;br&gt;
Lightweight terminal-first design&lt;/p&gt;
&lt;p&gt;The focus remains on usability rather than visual complexity.&lt;br&gt;
Getting Started&lt;br&gt;
Installation&lt;br&gt;
git clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mahinshanazeer/friday.git&quot;&gt;https://github.com/mahinshanazeer/friday.git&lt;/a&gt; &amp;amp;&amp;amp; cd friday &amp;amp;&amp;amp; bash install.sh&lt;br&gt;
First Run&lt;br&gt;
friday&lt;br&gt;
Authenticate using /login, and the session persists for future use.&lt;br&gt;
Uninstall&lt;br&gt;
cd friday &amp;amp;&amp;amp; bash uninstall.sh&lt;br&gt;
Key Learnings&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Provider APIs Differ Significantly&lt;br&gt;
Function-calling implementations vary widely across providers, requiring a unified abstraction layer.&lt;/li&gt;
&lt;li&gt;Terminal UX Matters&lt;br&gt;
Design improvements significantly impact usability and adoption.&lt;/li&gt;
&lt;li&gt;Reliability Is Critical&lt;br&gt;
Graceful handling of missing dependencies or failures ensures stability.&lt;/li&gt;
&lt;li&gt;Persistence Improves Experience&lt;br&gt;
Credential storage transformed the tool from experimental to practical.&lt;br&gt;
Built Using AI&lt;br&gt;
Friday itself was built using AI-assisted development:&lt;br&gt;
Antigravity - architecture and scaffolding&lt;br&gt;
GitHub Copilot - inline coding&lt;br&gt;
Claude - design validation and reviews&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Workflow:&lt;br&gt;
Define architecture via prompts&lt;br&gt;
Iteratively build features&lt;br&gt;
Debug and refine using AI&lt;/p&gt;
&lt;p&gt;Result: A production-ready system built in hours rather than weeks.&lt;br&gt;
Roadmap&lt;br&gt;
Planned improvements include:&lt;br&gt;
Streaming responses&lt;br&gt;
Chat export functionality&lt;br&gt;
Plugin architecture&lt;br&gt;
Multi-modal input (image support)&lt;br&gt;
Git integration&lt;/p&gt;
&lt;p&gt;Repository&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mahinshanazeer/friday&quot;&gt;https://github.com/mahinshanazeer/friday&lt;/a&gt;&lt;br&gt;
git clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mahinshanazeer/friday.git&quot;&gt;https://github.com/mahinshanazeer/friday.git&lt;/a&gt; &amp;amp;&amp;amp; cd friday &amp;amp;&amp;amp; bash install.sh&lt;br&gt;
Closing Note&lt;br&gt;
Friday is built on a simple principle:&lt;br&gt;
Your terminal should not just execute commands - it should assist, decide, and act.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14514/building-friday-a-multi-provider-ai-agent-that-lives-in-your-terminal</guid>
<pubDate>Sat, 11 Apr 2026 16:07:32 +0000</pubDate>
</item>
<item>
<title>CI/CD That Ships Itself: Our GitHub Actions Blueprint</title>
<link>https://coderlegion.com/8322/ci-cd-that-ships-itself-our-github-actions-blueprint</link>
<description>&lt;h2 id=&quot;ci-cd-that-ships-itself-our-github-actions-blueprint&quot;&gt;CI/CD That Ships Itself: Our GitHub Actions Blueprint&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;(Pocket Portfolio — 12 Weeks of Shipping)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fast releases are only useful if they are consistent and safe.&lt;br&gt;
This post breaks down Pocket Portfolio’s CI/CD pipeline on GitHub Actions: how we lint, type-check, test against Firebase emulators, run Playwright smoke tests, and then canary-deploy to the Edge.&lt;/p&gt;
&lt;p&gt;The goal is simple: every commit proves it can ship itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;pipeline-overview&quot;&gt;Pipeline Overview&lt;/h3&gt;
&lt;p&gt;Stages executed per pull request and on main:&lt;/p&gt;
&lt;p&gt;1) Lint and TypeCheck&lt;br&gt;
2) Build with reproducible caching&lt;br&gt;
3) Firebase emulator tests (unit and integration)&lt;br&gt;
4) Playwright UI smoke tests&lt;br&gt;
5) Artifact capture (build, coverage, screenshots)&lt;br&gt;
6) Canary deploy to &lt;code&gt;next.pocketportfolio.app&lt;/code&gt; with manual approval&lt;br&gt;
7) Post-deploy health checks and auto-rollback&lt;/p&gt;
&lt;p&gt;We use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Actions matrix to cover Node versions and Next.js runtime modes  &lt;/li&gt;
&lt;li&gt;Project-level concurrency to avoid clobbering environments  &lt;/li&gt;
&lt;li&gt;Required checks enforced by branch protection rules&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;repository-structure-relevant-parts-&quot;&gt;Repository Structure (relevant parts)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
.github/
workflows/
ci.yml
deploy-canary.yml
apps/
web/                  # Next.js 14 app
packages/
ui/                   # shared components
config/               # ESLint, TS config
tests/
e2e/                  # Playwright specs
seeds/                # Firebase test data

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;workflow-1-ci-pr-and-main-&quot;&gt;Workflow 1: CI (PR and main)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;./github/workflows/ci.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: CI

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  setup:
    name: Setup
    runs-on: ubuntu-latest
    outputs:
      node: ${{ steps.matrix.outputs.node }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - name: Install
        run: pnpm install --frozen-lockfile

  lint_typecheck:
    name: Lint and TypeCheck
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - run: pnpm eslint .
      - run: pnpm tsc -p tsconfig.json --noEmit

  build:
    name: Build
    needs: [setup, lint_typecheck]
    runs-on: ubuntu-latest
    env:
      NEXT_TELEMETRY_DISABLED: 1
      NODE_ENV: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Build web
        run: pnpm --filter ./apps/web build
      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: web-build
          path: apps/web/.next

  test_emulators:
    name: Firebase Emulator Tests
    needs: build
    runs-on: ubuntu-latest
    services:
      firestore:
        image: docker.io/bitnami/firebase-emulator:latest
        ports:
          - 4000:4000
          - 8080:8080
          - 9099:9099
          - 9199:9199
    env:
      FIREBASE_AUTH_EMULATOR_HOST: localhost:9099
      FIRESTORE_EMULATOR_HOST: localhost:8080
      GCLOUD_PROJECT: test-project
      NODE_ENV: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Seed test data
        run: pnpm tsx tests/seeds/seed.ts
      - name: Unit and integration tests
        run: pnpm test -- --reporter=junit --coverage
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage

  e2e_smoke:
    name: Playwright Smoke (headless)
    needs: test_emulators
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Install browsers
        run: pnpm exec playwright install --with-deps
      - name: Run smoke tests
        env:
          BASE_URL: http://localhost:3000
        run: |
          pnpm --filter ./apps/web dev &amp;amp; sleep 8
          pnpm exec playwright test tests/e2e/smoke.spec.ts --reporter=line
      - name: Upload Playwright artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-artifacts
          path: |
            playwright-report
            test-results
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cancels superseded runs automatically&lt;/li&gt;
&lt;li&gt;Builds once and reuses artifacts&lt;/li&gt;
&lt;li&gt;Spins Firebase emulators for realistic tests&lt;/li&gt;
&lt;li&gt;Runs Playwright in headless mode against a local dev server&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;workflow-2-canary-deploy-to-edge&quot;&gt;Workflow 2: Canary Deploy to Edge&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;./github/workflows/deploy-canary.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Deploy Canary

on:
  workflow_run:
    workflows: [&quot;CI&quot;]
    types: [completed]
    branches: [ main ]

permissions:
  contents: read
  deployments: write
  id-token: write

concurrency:
  group: deploy-canary
  cancel-in-progress: false

jobs:
  gate:
    name: Require green CI
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - run: echo &quot;CI passed, proceeding to deploy.&quot;

  deploy:
    name: Deploy to next.pocketportfolio.app
    needs: gate
    runs-on: ubuntu-latest
    environment:
      name: canary
      url: https://next.pocketportfolio.app
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Build
        run: pnpm --filter ./apps/web build
      - name: Vercel Deploy (Edge)
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          working-directory: ./apps/web
          scope: ${{ secrets.VERCEL_ORG_ID }}
          prod: false
      - name: Health check
        run: |
          curl -fsSL https://next.pocketportfolio.app/api/health || exit 1
          curl -fsSL https://next.pocketportfolio.app/api/health-price || exit 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;workflow_run&lt;/code&gt; ensures deploy runs only after CI succeeds on &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Environment protection rules can require manual approval inside GitHub before the &lt;code&gt;deploy&lt;/code&gt; job starts.&lt;/li&gt;
&lt;li&gt;We target Edge by configuring Vercel project functions to run on the Edge runtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;health-checks-and-auto-rollback&quot;&gt;Health Checks and Auto-Rollback&lt;/h3&gt;
&lt;p&gt;We keep deploys safe with a simple pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After deployment, curl health and quote endpoints&lt;/li&gt;
&lt;li&gt;If any return non-2xx, mark the job failed&lt;/li&gt;
&lt;li&gt;A failing canary keeps production untouched&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Optional rollback step (if you use Vercel Protected Branches):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;      - name: Roll back canary to previous deployment
        if: failure()
        run: |
          echo &quot;Rolling back...&quot;
          # Example: pin to previous successful alias or commit sha via Vercel CLI
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;speed-and-stability-techniques&quot;&gt;Speed and Stability Techniques&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Caching:&lt;/strong&gt; &lt;code&gt;actions/setup-node&lt;/code&gt; with &lt;code&gt;cache: pnpm&lt;/code&gt; cuts install time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency:&lt;/strong&gt; single deploy job at a time prevents environment drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artifacts:&lt;/strong&gt; store build output and Playwright results for debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate concerns:&lt;/strong&gt; CI proves code is safe; deploy workflow just ships.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Emulators:&lt;/strong&gt; Firebase emulator suite catches integration issues locally and in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;failure-modes-we-handle&quot;&gt;Failure Modes We Handle&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Stuck third-party provider: Playwright tests stub provider responses for critical flows.&lt;/li&gt;
&lt;li&gt;Flaky UI timing: Playwright uses test id selectors and realistic waits.&lt;/li&gt;
&lt;li&gt;Race conditions: matrix and concurrency settings keep builds deterministic.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;minimal-smoke-spec-example&quot;&gt;Minimal Smoke Spec Example&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tests/e2e/smoke.spec.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { test, expect } from '@playwright/test';

test('dashboard loads and shows health badge', async ({ page }) =&amp;gt; {
  await page.goto('/');
  await expect(page.getByTestId('health-badge')).toHaveText(/healthy|degraded/);
});

test('price endpoint responds', async ({ request }) =&amp;gt; {
  const res = await request.get('/api/health-price');
  expect(res.ok()).toBeTruthy();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-to-measure&quot;&gt;What To Measure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CI total duration (target: under 8 minutes for PRs)&lt;/li&gt;
&lt;li&gt;Flake rate (target: below 1 percent over 30 days)&lt;/li&gt;
&lt;li&gt;Canary failure rate and mean time to recovery&lt;/li&gt;
&lt;li&gt;Playwright runtime and retries per suite&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;assets&quot;&gt;Assets&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Workflow diagram (Setup -&amp;gt; Lint -&amp;gt; Build -&amp;gt; Emulators -&amp;gt; Playwright -&amp;gt; Canary Deploy)&lt;/li&gt;
&lt;li&gt;Screenshots of GitHub Checks and Environment approvals&lt;/li&gt;
&lt;li&gt;Example Playwright report&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Actions runs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/PocketPortfolio/Financialprofilenetwork/actions&quot;&gt;https://github.com/PocketPortfolio/Financialprofilenetwork/actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;App: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pocketportfolio.app&quot;&gt;https://pocketportfolio.app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;Every commit runs the same path production will run: build, test, verify, deploy, and prove it is healthy.&lt;br&gt;
If any step fails, the release stops itself. That is CI/CD that ships itself.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8322/ci-cd-that-ships-itself-our-github-actions-blueprint</guid>
<pubDate>Tue, 09 Dec 2025 12:06:26 +0000</pubDate>
</item>
<item>
<title> CI/CD Security Mistake: Are You Giving Your Build Container Root Access to Your Server? </title>
<link>https://coderlegion.com/6992/ci-cd-security-mistake-are-you-giving-your-build-container-root-access-to-your-server</link>
<description>&lt;h3 id=&quot;the-dangerous-truth-about-running-docker-inside-docker-dind-vs-dood-&quot;&gt;The Dangerous Truth About Running Docker Inside Docker (DinD vs. DooD)&lt;/h3&gt;
&lt;p&gt;A robust Continuous Integration/Continuous Delivery (CI/CD) pipeline often requires building, testing, or pushing new container images. To do this from within your Jenkins, GitLab, or GitHub Actions agent, you need access to a Docker daemon.&lt;/p&gt;
&lt;p&gt;This need often leads developers to adopt one of two patterns, and choosing the wrong one is one of the most common and critical security mistakes you can make in production. This isn't just about convenience; it's about host security.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;pattern-1-docker-out-of-docker-dood---the-hidden-trap-&quot;&gt;Pattern 1: Docker-Out-of-Docker (DooD) - &lt;strong&gt;The Hidden Trap&lt;/strong&gt; ⚠️&lt;/h3&gt;
&lt;p&gt;DooD is the most common pattern because it's the easiest to set up, but it is fundamentally dangerous.&lt;/p&gt;
&lt;h4 id=&quot;the-mechanism&quot;&gt;The Mechanism&lt;/h4&gt;
&lt;p&gt;In the DooD pattern, your build container (e.g., your Jenkins Agent) does &lt;strong&gt;not&lt;/strong&gt; run its own Docker daemon. Instead, you mount the host machine's Docker socket into the container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# docker-compose.yml (DooD Example)
volumes:
  - /var/run/docker.sock:/var/run/docker.sock
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-security-problem-root-escape&quot;&gt;The Security Problem: Root Escape&lt;/h4&gt;
&lt;p&gt;The Docker socket (&lt;code&gt;/var/run/docker.sock&lt;/code&gt;) is the gateway to the Docker Engine, which runs with &lt;strong&gt;root privileges&lt;/strong&gt; on your host server. By mounting this socket, you grant the container full, unrestricted control over the entire host's Docker environment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Risk:&lt;/strong&gt; A successful exploit (e.g., a vulnerable dependency in your build tool) inside your container can execute a command like this:&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# A single command to escape the container sandbox:
docker run -v /:/host_root -it ubuntu sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command instantly mounts the host server's root filesystem (&lt;code&gt;/&lt;/code&gt;) into the new container, giving the attacker root access to your entire build machine. &lt;strong&gt;This is a complete violation of the Principle of Least Privilege.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-pattern-2-docker-in-docker-dind---the-secure-standard&quot;&gt;️ Pattern 2: Docker-in-Docker (DinD) - &lt;strong&gt;The Secure Standard&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;DinD is the secure, production-ready solution that isolates your build processes.&lt;/p&gt;
&lt;h4 id=&quot;the-mechanism-1&quot;&gt;The Mechanism&lt;/h4&gt;
&lt;p&gt;With DinD, you don't use the host's socket. Instead, you launch a &lt;strong&gt;separate, isolated container&lt;/strong&gt; dedicated solely to running a Docker daemon. Your build agent then communicates with this isolated daemon over the internal network.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Isolated Daemon Container:&lt;/strong&gt; Runs a dedicated &lt;code&gt;dind&lt;/code&gt; image (e.g., &lt;code&gt;docker:dind&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client Container (Agent):&lt;/strong&gt; Runs a container that has the Docker CLI installed and uses the &lt;code&gt;DOCKER_HOST&lt;/code&gt; environment variable to connect to the isolated daemon.&lt;/li&gt;
&lt;/ol&gt;
&amp;lt;!-- end list --&amp;gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# docker-compose.yml (DinD Key Configuration)

jenkins-agent:
  image: custom-agent-with-docker-cli
  environment:
    # This redirects the agent's Docker commands to the isolated service!
    DOCKER_HOST: tcp://jenkins-dind:2375

jenkins-dind:
  image: docker:dind
  privileged: true # This container needs elevated privileges to run its own daemon
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-security-solution-perfect-isolation&quot;&gt;The Security Solution: Perfect Isolation&lt;/h4&gt;
&lt;p&gt;The agent communicates with a &lt;strong&gt;disposable, sandboxed environment&lt;/strong&gt;. If the agent or any container it spawns is compromised, the attacker only gains control over the isolated DinD daemon and its temporary files. &lt;strong&gt;They cannot touch your host server's file system or other critical services.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;your-takeaway&quot;&gt;Your Takeaway&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Pattern &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Connection Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Security Posture &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Best For&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;DooD&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Mounts Host Socket (&lt;code&gt;/var/run/docker.sock&lt;/code&gt;) &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Low - Gives Host Root Access&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Local development, quick proofs-of-concept.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;DinD&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Network Connection (&lt;code&gt;DOCKER_HOST&lt;/code&gt;) &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;High - Isolates Risk from Host&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;All production CI/CD pipelines.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Don't sacrifice host security for convenience. If you must run Docker inside Docker, ensure you are using the &lt;strong&gt;DinD pattern&lt;/strong&gt; to keep your production environment safe.&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/6992/ci-cd-security-mistake-are-you-giving-your-build-container-root-access-to-your-server</guid>
<pubDate>Sat, 01 Nov 2025 20:48:28 +0000</pubDate>
</item>
<item>
<title>From Code Push to Docker Hub: CI/CD with GitHub Actions</title>
<link>https://coderlegion.com/6405/from-code-push-to-docker-hub-ci-cd-with-github-actions</link>
<description>&lt;p&gt;Imagine you’re running a bakery. Every morning, you bring fresh ingredients (your new code). But instead of kneading, baking, packaging, and delivering each loaf by hand, you want a &lt;strong&gt;conveyor belt&lt;/strong&gt; that handles everything the moment the ingredients arrive.&lt;/p&gt;
&lt;p&gt;That conveyor belt is your &lt;strong&gt;CI/CD pipeline&lt;/strong&gt;.&lt;br&gt;
Each time you &lt;code&gt;git push&lt;/code&gt;, it builds, tests, packages, and ships your product automatically.&lt;/p&gt;
&lt;p&gt;In this article, we’ll create that conveyor belt for your Node.js app using &lt;code&gt;GitHub Actions and Docker&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14288477006382423933&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-automate-docker-builds-&quot;&gt;Why Automate Docker Builds?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
Manual builds are slow, repetitive, and error-prone. Automation brings consistency and peace of mind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explain Like a Friend&lt;/strong&gt;&lt;br&gt;
Think of manually assembling furniture for every order — you’ll eventually forget screws or swap panels. A factory line ensures the same result every single time. CI/CD is that factory line for software.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;&lt;br&gt;
A small dev team pushes hotfixes daily. Without automation, they rebuild locally, tag manually, and sometimes push to the wrong repo. With GitHub Actions, every push triggers a clean, predictable build → tag → push pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Action Step&lt;/strong&gt;&lt;br&gt;
Write down every manual command you currently run to deploy your Docker image. That’s your “to-automate” checklist.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites-accounts-secrets-amp-permissions&quot;&gt;Prerequisites: Accounts, Secrets &amp;amp; Permissions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
Your automation can’t log in or deploy if it doesn’t have the right keys.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;br&gt;
Think of GitHub and Docker Hub as two warehouses. Your CI/CD robot needs secure keys to both: one to collect the code, the other to store the built product (your image).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;&lt;br&gt;
In the demo, the author logs into Docker Hub using encrypted secrets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOCKER_HUB_USERNAME&lt;/li&gt;
&lt;li&gt;DOCKER_HUB_TOKEN&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These secrets are stored safely in &lt;strong&gt;GitHub → Settings → Secrets → Actions&lt;/strong&gt; never in your source code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Action Steps&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create or log into your Docker Hub account.&lt;/li&gt;
&lt;li&gt;Generate a personal access token (instead of using your password).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In GitHub, go to Settings → Secrets → Actions, and add:&lt;/p&gt;
&lt;p&gt;DOCKER_HUB_USERNAME&lt;/p&gt;
&lt;p&gt;DOCKER_HUB_TOKEN&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;writing-the-workflow-main-yaml-step-by-step&quot;&gt;Writing the Workflow (main.yaml) — Step by Step&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
The workflow file is your recipe — the step-by-step plan your robot will follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;br&gt;
In &lt;code&gt;.github/workflows/main.yaml&lt;/code&gt;, you define:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When to run: (trigger event, like push)&lt;/li&gt;
&lt;li&gt;What to run: (the jobs and steps)&lt;/li&gt;
&lt;li&gt;How to run: (what tools or actions to use)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key Parts&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;on: push&lt;/code&gt;: “Start the oven when new ingredients arrive.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;jobs&lt;/code&gt;: “Each job is a workstation — baking, packaging, shipping.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;actions/checkout@v3&lt;/code&gt;: “Fetch ingredients from the storage (repo).”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker/login-action@v2&lt;/code&gt;: “Unlock the warehouse with your keycard (secret).”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker/build-push-action@v3&lt;/code&gt;: “Bake and ship the final product.”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example Workflow&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Build and Push Docker Image

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}

      - uses: docker/build-push-action@v3
        with:
          context: .
          push: true
          tags: yourname/app:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Common Pitfalls&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Wrong &lt;strong&gt;Dockerfile path&lt;/strong&gt; → build fails.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Missing or misnamed secrets → login fails silently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Forgot &lt;code&gt;push: true&lt;/code&gt; → image never uploads.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;✅ Action Step&lt;/strong&gt;&lt;br&gt;
Commit your file to &lt;code&gt;.github/workflows/main.yaml&lt;/code&gt; and push it. Your pipeline is ready.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;build-amp-push-watching-it-run&quot;&gt;Build &amp;amp; Push: Watching It Run&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
Seeing it work builds confidence that your automation truly works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;br&gt;
Push your code, and watch:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GitHub detects the push.&lt;/li&gt;
&lt;li&gt;Actions triggers the job.&lt;/li&gt;
&lt;li&gt;The workflow checks out code, logs in to Docker Hub, builds, and pushes the image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;&lt;br&gt;
In the demo, after pushing a small code change, the author refreshed Docker Hub and saw the new image tagged &lt;code&gt;latest&lt;/code&gt;. No manual typing, no Docker commands.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Action Steps&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edit a small file (e.g., README).&lt;/li&gt;
&lt;li&gt;Run git push.&lt;/li&gt;
&lt;li&gt;Open GitHub → Actions tab → watch logs.&lt;/li&gt;
&lt;li&gt;Open Docker Hub → confirm the new tag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Counterpoints &amp;amp; Limitations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Private repos or registries&lt;/strong&gt;: Require additional permissions or tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-service setups&lt;/strong&gt;: Need multiple jobs or build matrix strategies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large images&lt;/strong&gt;: Consider caching layers for faster builds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;: Always scan base images and protect secrets carefully.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;mini-case-study-solo-developer-to-one-click-deployment-&quot;&gt;Mini Case Study: “Solo Developer to One-Click Deployment”&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;:&lt;br&gt;
X, a solo Node.js developer, manually built and pushed Docker images every update — often forgetting tags or pushing the wrong version.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After GitHub Actions&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added main.yaml&lt;/li&gt;
&lt;li&gt;Configured Docker Hub secrets&lt;/li&gt;
&lt;li&gt;Now, every git push builds + pushes automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He cut his deployment time from 10 minutes to under 1, and never mistakes again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-from-kitchen-chaos-to-conveyor-belt&quot;&gt;Conclusion — From Kitchen Chaos to Conveyor Belt&lt;/h3&gt;
&lt;p&gt;We began with a bakery. You don’t want to bake, pack, and ship every loaf by hand. You want a conveyor belt that works reliably.&lt;/p&gt;
&lt;p&gt;GitHub Actions and Docker give you that belt. As soon as you push code, your automation handles the rest — repeatable, consistent, hands-off.&lt;/p&gt;
&lt;p&gt;Take this workflow file, plug in your secrets, push a change, and watch your pipeline come alive.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/6405/from-code-push-to-docker-hub-ci-cd-with-github-actions</guid>
<pubDate>Tue, 14 Oct 2025 05:04:53 +0000</pubDate>
</item>
<item>
<title>Automate your open-source project's builds &amp; deployments with CI/CD using GitHub Actions!</title>
<link>https://coderlegion.com/5883/automate-your-open-source-projects-builds-deployments-with-ci-cd-using-github-actions</link>
<description>&lt;h3 id=&quot;building-ci-cd-pipelines-without-losing-your-mind-&quot;&gt;Building CI/CD Pipelines (Without Losing Your Mind)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Beginner Level&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hey everyone!  As members of the open-source community, we're all passionate about building awesome things – from libraries that power the internet to tools that make developers’ lives easier. But sometimes, getting those amazing contributions into production can feel… well, a bit like herding cats. That's where CI/CD pipelines come in!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://semaphore.io/wp-content/uploads/2022/05/cicd-pipeline-introduction-1024x422-1.jpg&quot; alt=&quot;CICD Pipeline&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What &lt;em&gt;is&lt;/em&gt; a CI/CD Pipeline, Anyway? (Don't worry, it's not as scary as it sounds)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CI/CD stands for Continuous Integration and Continuous Delivery/Deployment.  Think of it as an automated assembly line for your code. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration (CI):&lt;/strong&gt; Whenever someone commits code to your repository, the CI system automatically pulls that code, builds it, runs tests, and checks for integration issues. It's like constantly merging small updates instead of waiting for a massive, potentially disastrous, merge at the end.  This helps catch bugs early and keeps your codebase healthy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Delivery (CD):&lt;/strong&gt; Takes the CI step a little further by automating the process of releasing your code to a staging or testing environment. It ensures that your software is always in a deployable state. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Deployment (also CD):&lt;/strong&gt;  The ultimate goal! Automates the entire release process, including deploying code directly to production after passing all tests.  This allows for faster feedback loops and quicker delivery of value to users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why Should We Care? (Besides Saving Our Sanity)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For open-source projects, a solid CI/CD pipeline offers huge benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faster Feedback:&lt;/strong&gt; Developers get instant notifications when their code breaks something.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced Risk:&lt;/strong&gt; Automated tests catch bugs before they reach users.  (Nobody likes a surprise bug!)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Increased Confidence:&lt;/strong&gt; Knowing your code is consistently tested and deployed gives you peace of mind.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier Collaboration:&lt;/strong&gt; Everyone benefits from clear, automated workflows. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More Time for Innovation:&lt;/strong&gt; Less time spent on manual deployment means more time coding!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Building Your Own Pipeline&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Okay, enough theory. Let's dive into how to build a CI/CD pipeline.  There are tons of tools out there – Jenkins, GitLab CI, GitHub Actions, CircleCI, Azure DevOps… the list goes on!  Don’t feel overwhelmed; you don't need to master &lt;em&gt;everything&lt;/em&gt; at once.&lt;/p&gt;
&lt;p&gt;I'll focus on &lt;strong&gt;GitHub Actions&lt;/strong&gt; here, as it's widely used and integrates seamlessly with GitHub repositories (which is probably where your awesome open-source projects live). It also has a generous free tier – perfect for getting started!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here’s a basic workflow example:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a &lt;code&gt;.github/workflows&lt;/code&gt; directory in your repository.&lt;/strong&gt; This is where you'll store your workflow files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a YAML file (e.g., &lt;code&gt;main.yml&lt;/code&gt;) inside the directory.&lt;/strong&gt; This file defines the steps of your pipeline.  Here’s an example:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: CI/CD Pipeline

on:
  push:
    branches:
      - main  # Trigger the workflow on pushes to the 'main' branch

jobs:
  build:
    runs-on: ubuntu-latest  # Use a Linux runner

    steps:
      - uses: actions/checkout@v3 # Checkout the code from the repository

      - name: Set up Node.js 16
        uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test  # Replace with your testing command (e.g., pytest, rspec)

      - name: Build the project
        run: npm run build # Replace with your build command

      - name: Deploy to staging (optional)
        if: github.ref == 'refs/heads/main'
        run: |
          # Add your deployment commands here - e.g., using SSH, cloud provider CLI tools etc.
          echo &quot;Deploying to staging...&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Let’s break down that YAML:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: A descriptive name for your workflow.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on&lt;/code&gt;:  Specifies when the workflow should run (in this case, on pushes to the &lt;code&gt;main&lt;/code&gt; branch).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jobs&lt;/code&gt;: Defines a set of tasks to be executed in parallel or sequentially. Here we have one job called &lt;code&gt;build&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runs-on&lt;/code&gt;: Specifies the virtual environment where your job will run.  &lt;code&gt;ubuntu-latest&lt;/code&gt; is a common choice.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;steps&lt;/code&gt;: A list of individual tasks to perform within the job. Each step uses pre-built actions from the GitHub Actions marketplace (like &lt;code&gt;actions/checkout@v3&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Important Notes and Tips:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Actions Marketplace:&lt;/strong&gt; The GitHub Actions marketplace has &lt;em&gt;tons&lt;/em&gt; of community-created actions for everything from linting your code to sending Slack notifications on build failures.  Explore it!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets Management:&lt;/strong&gt; Don't hardcode API keys or passwords in your workflow files! Use GitHub Secrets (Settings -&amp;gt; Secrets -&amp;gt; Actions) instead. It's much safer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing is Key:&lt;/strong&gt; Write good tests! They are the foundation of a robust CI/CD pipeline.  Aim for high test coverage. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branching Strategy:&lt;/strong&gt;  Consider adopting a branching strategy like Gitflow to streamline your development process. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notifications:&lt;/strong&gt; Configure notifications (e.g., Slack, email) to be alerted about build failures and deployments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Troubleshooting: Because Things &lt;em&gt;Always&lt;/em&gt; Go Wrong&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Expect things to break! Your first pipeline won’t be perfect. Here are a few common troubleshooting tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Check the logs:&lt;/strong&gt; The GitHub Actions UI provides detailed logs for each step of your workflow.  Read them carefully!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissions:&lt;/strong&gt; Make sure your workflow has the necessary permissions to access your repository and deploy to your target environment. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependencies:&lt;/strong&gt; Double-check that all dependencies are installed correctly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don't Panic&lt;/strong&gt;: Seriously, it happens to everyone. Debugging is part of the process!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Resources to Explore&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions&quot;&gt;https://docs.github.com/en/actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.gitlab.com/ee/ci/&quot;&gt;https://docs.gitlab.com/ee/ci/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CircleCI Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://circleci.com/docs/&quot;&gt;https://circleci.com/docs/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Takeaway&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Building a CI/CD pipeline is an investment in the future of your open-source project. It might seem daunting at first, but it’s worth the effort.  By automating your build, test, and deployment processes, you can free up time to focus on what truly matters – writing awesome code!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now go forth and automate! And if you have any questions or tips of your own, share them in the comments below!&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(P.S. If you accidentally deploy a broken version of your project to production, just blame the bot.  It's always a good strategy.) &lt;/strong&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/5883/automate-your-open-source-projects-builds-deployments-with-ci-cd-using-github-actions</guid>
<pubDate>Thu, 02 Oct 2025 07:00:54 +0000</pubDate>
</item>
</channel>
</rss>